Spaces:
Sleeping
Sleeping
Commit ·
084eeb2
1
Parent(s): 95aba55
Feat: Sprint 4 - Update with smarter versions
Browse files- src/agent.py +3 -3
- src/finance_tools.py +19 -16
src/agent.py
CHANGED
|
@@ -91,9 +91,9 @@ async def analyze_stock(state: AgentState):
|
|
| 91 |
HARD DATA INPUTS:
|
| 92 |
- Sector: {sector}
|
| 93 |
- Current Price: ${metrics.get('current_price')}
|
| 94 |
-
- Graham Intrinsic Value: ${graham_val}
|
| 95 |
-
-
|
| 96 |
-
- Debt/Equity: {metrics.get('debt_to_equity')}
|
| 97 |
|
| 98 |
QUALITATIVE INPUTS (News & Search):
|
| 99 |
{market_news}
|
|
|
|
| 91 |
HARD DATA INPUTS:
|
| 92 |
- Sector: {sector}
|
| 93 |
- Current Price: ${metrics.get('current_price')}
|
| 94 |
+
- Graham Intrinsic Value: ${graham_val} (Conservative)
|
| 95 |
+
- PEG Ratio (Growth Value): {metrics.get('peg_ratio')} (Target < 2.0 for Tech)
|
| 96 |
+
- Debt/Equity: {metrics.get('debt_to_equity')} (Note: Low % is Good)
|
| 97 |
|
| 98 |
QUALITATIVE INPUTS (News & Search):
|
| 99 |
{market_news}
|
src/finance_tools.py
CHANGED
|
@@ -45,31 +45,35 @@ def calculate_graham_number(info):
|
|
| 45 |
|
| 46 |
def check_financial_health(ticker):
|
| 47 |
"""
|
| 48 |
-
The 'Graham & Buffett' Gatekeeper.
|
| 49 |
"""
|
| 50 |
try:
|
| 51 |
stock = yf.Ticker(ticker)
|
| 52 |
-
# Fast info is faster, but 'info' has the deep balance sheet data we need
|
| 53 |
info = stock.info
|
| 54 |
|
| 55 |
sector = info.get('sector', 'Default')
|
| 56 |
config = SECTOR_CONFIG.get(sector, SECTOR_CONFIG['Default'])
|
| 57 |
|
| 58 |
-
|
|
|
|
|
|
|
|
|
|
| 59 |
|
| 60 |
-
# ---
|
| 61 |
-
#
|
|
|
|
|
|
|
|
|
|
|
|
|
| 62 |
current_ratio = info.get('currentRatio')
|
| 63 |
if current_ratio and current_ratio < 1.0:
|
| 64 |
return {"status": "FAIL", "reason": f"Graham Reject: Liquidity Crisis (Current Ratio {current_ratio} < 1.0)"}
|
| 65 |
|
| 66 |
# --- 2. SECTOR SPECIFIC DEBT CHECK ---
|
| 67 |
-
# If it's NOT a bank, we check Debt/EBITDA
|
| 68 |
if not config['exclude_ebitda']:
|
| 69 |
ebitda = info.get('ebitda')
|
| 70 |
debt = info.get('totalDebt')
|
| 71 |
cash = info.get('totalCash')
|
| 72 |
-
|
| 73 |
if ebitda and debt and ebitda > 0:
|
| 74 |
net_debt_ebitda = (debt - cash) / ebitda
|
| 75 |
if net_debt_ebitda > config['debt_max']:
|
|
@@ -79,27 +83,26 @@ def check_financial_health(ticker):
|
|
| 79 |
intrinsic_val = calculate_graham_number(info)
|
| 80 |
current_price = info.get('currentPrice', 0)
|
| 81 |
|
| 82 |
-
margin_of_safety =
|
| 83 |
if intrinsic_val > 0 and current_price > 0:
|
| 84 |
-
|
|
|
|
| 85 |
|
| 86 |
-
# Pass specific metrics to the Agent for the final report
|
| 87 |
metrics = {
|
| 88 |
"sector": sector,
|
| 89 |
"current_price": current_price,
|
| 90 |
"intrinsic_value": round(intrinsic_val, 2),
|
| 91 |
-
"margin_of_safety":
|
| 92 |
-
"debt_to_equity":
|
| 93 |
-
"
|
| 94 |
-
"
|
| 95 |
}
|
| 96 |
|
| 97 |
return {
|
| 98 |
"status": "PASS",
|
| 99 |
-
"reason": f"Solvent. Sector: {sector}.
|
| 100 |
"metrics": metrics
|
| 101 |
}
|
| 102 |
|
| 103 |
except Exception as e:
|
| 104 |
-
# If data fails, we default to PASS but warn the agent
|
| 105 |
return {"status": "PASS", "reason": f"Data Warning: {str(e)}", "metrics": {}}
|
|
|
|
| 45 |
|
| 46 |
def check_financial_health(ticker):
|
| 47 |
"""
|
| 48 |
+
The 'Graham & Buffett' Gatekeeper (Fixed for Percentage Bugs).
|
| 49 |
"""
|
| 50 |
try:
|
| 51 |
stock = yf.Ticker(ticker)
|
|
|
|
| 52 |
info = stock.info
|
| 53 |
|
| 54 |
sector = info.get('sector', 'Default')
|
| 55 |
config = SECTOR_CONFIG.get(sector, SECTOR_CONFIG['Default'])
|
| 56 |
|
| 57 |
+
# --- FIX 1: FORMAT DEBT CORRECTLY ---
|
| 58 |
+
# yfinance returns 15.5 for 15.5%. We convert to decimal for math, but string for LLM.
|
| 59 |
+
debt_equity_raw = info.get('debtToEquity', 0)
|
| 60 |
+
debt_equity_str = f"{debt_equity_raw}%" if debt_equity_raw else "N/A"
|
| 61 |
|
| 62 |
+
# --- FIX 2: ADD GROWTH VALUATION (PEG RATIO) ---
|
| 63 |
+
# For Tech, we look at PEG (Price/Earnings to Growth).
|
| 64 |
+
# < 1.0 is Undervalued, < 2.0 is Fair for Quality.
|
| 65 |
+
peg_ratio = info.get('pegRatio', 'N/A')
|
| 66 |
+
|
| 67 |
+
# --- 1. GRAHAM'S SOLVENCY CHECK ---
|
| 68 |
current_ratio = info.get('currentRatio')
|
| 69 |
if current_ratio and current_ratio < 1.0:
|
| 70 |
return {"status": "FAIL", "reason": f"Graham Reject: Liquidity Crisis (Current Ratio {current_ratio} < 1.0)"}
|
| 71 |
|
| 72 |
# --- 2. SECTOR SPECIFIC DEBT CHECK ---
|
|
|
|
| 73 |
if not config['exclude_ebitda']:
|
| 74 |
ebitda = info.get('ebitda')
|
| 75 |
debt = info.get('totalDebt')
|
| 76 |
cash = info.get('totalCash')
|
|
|
|
| 77 |
if ebitda and debt and ebitda > 0:
|
| 78 |
net_debt_ebitda = (debt - cash) / ebitda
|
| 79 |
if net_debt_ebitda > config['debt_max']:
|
|
|
|
| 83 |
intrinsic_val = calculate_graham_number(info)
|
| 84 |
current_price = info.get('currentPrice', 0)
|
| 85 |
|
| 86 |
+
margin_of_safety = "N/A"
|
| 87 |
if intrinsic_val > 0 and current_price > 0:
|
| 88 |
+
margin = (intrinsic_val - current_price) / intrinsic_val * 100
|
| 89 |
+
margin_of_safety = f"{round(margin, 1)}%"
|
| 90 |
|
|
|
|
| 91 |
metrics = {
|
| 92 |
"sector": sector,
|
| 93 |
"current_price": current_price,
|
| 94 |
"intrinsic_value": round(intrinsic_val, 2),
|
| 95 |
+
"margin_of_safety": margin_of_safety,
|
| 96 |
+
"debt_to_equity": debt_equity_str, # Now has '%' symbol
|
| 97 |
+
"peg_ratio": peg_ratio, # New Metric for Tech
|
| 98 |
+
"return_on_equity": f"{info.get('returnOnEquity', 0)*100:.2f}%" # Format as %
|
| 99 |
}
|
| 100 |
|
| 101 |
return {
|
| 102 |
"status": "PASS",
|
| 103 |
+
"reason": f"Solvent. Sector: {sector}. Safety: {margin_of_safety}",
|
| 104 |
"metrics": metrics
|
| 105 |
}
|
| 106 |
|
| 107 |
except Exception as e:
|
|
|
|
| 108 |
return {"status": "PASS", "reason": f"Data Warning: {str(e)}", "metrics": {}}
|