Spaces:
Sleeping
Sleeping
Update app.py
Browse files
app.py
CHANGED
|
@@ -1,5 +1,5 @@
|
|
| 1 |
"""
|
| 2 |
-
|
| 3 |
=========================================================================
|
| 4 |
Data integrity > model complexity. Intrinsic value > momentum. Margin of safety > prediction.
|
| 5 |
|
|
@@ -20,28 +20,38 @@ import plotly.graph_objects as go
|
|
| 20 |
import yfinance as yf
|
| 21 |
|
| 22 |
logging.basicConfig(level=logging.INFO, format="%(asctime)s | %(levelname)s | %(message)s")
|
| 23 |
-
logger = logging.getLogger("
|
| 24 |
-
|
| 25 |
-
|
| 26 |
-
#
|
| 27 |
-
"
|
| 28 |
-
"
|
| 29 |
-
|
| 30 |
-
"
|
| 31 |
-
"
|
| 32 |
-
|
| 33 |
-
|
| 34 |
-
"
|
| 35 |
-
|
| 36 |
-
"
|
| 37 |
-
|
| 38 |
-
"
|
| 39 |
-
|
| 40 |
-
"
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 41 |
]
|
| 42 |
-
|
| 43 |
-
assert len(
|
| 44 |
-
BIST_UNIVERSE = [s + ".IS" for s in BIST_UNIVERSE]
|
| 45 |
|
| 46 |
REQUEST_DELAY_SEC = 0.3
|
| 47 |
CACHE_TTL_HOURS = 6
|
|
@@ -124,8 +134,8 @@ def fp(x, d=1):
|
|
| 124 |
|
| 125 |
|
| 126 |
def norm_sym(t: str) -> str:
|
| 127 |
-
t = (t or "").strip().upper()
|
| 128 |
-
return t
|
| 129 |
|
| 130 |
|
| 131 |
def mk_params(infl, rw, rtg, rg, pt, cv) -> Dict[str, float]:
|
|
@@ -179,8 +189,8 @@ def segment_of(industry: str, sym: str) -> str:
|
|
| 179 |
return "Bank"
|
| 180 |
if "insurance" in ind:
|
| 181 |
return "Insurance"
|
| 182 |
-
if "reit" in ind or "real estate" in ind
|
| 183 |
-
return "
|
| 184 |
return "Industrial"
|
| 185 |
|
| 186 |
|
|
@@ -297,7 +307,7 @@ def fetch(symbol: str, force: bool = False) -> Optional[Dict[str, Any]]:
|
|
| 297 |
ev = (mcap + net_debt) if (mcap and net_debt is not None) else num(info.get("enterpriseValue"))
|
| 298 |
ebitda0 = s["ebitda"][0]
|
| 299 |
industry = info.get("industry") or "Unknown"
|
| 300 |
-
seg = segment_of(industry, symbol
|
| 301 |
roes = [div(s["ni"][i], s["eq"][i]) for i in range(3) if (s["eq"][i] or 0) > 0]
|
| 302 |
roes = [r for r in roes if r is not None]
|
| 303 |
ir = num(info.get("returnOnEquity"))
|
|
@@ -306,7 +316,7 @@ def fetch(symbol: str, force: bool = False) -> Optional[Dict[str, Any]]:
|
|
| 306 |
sust_roe = float(np.clip(np.mean(roes), -1, 1.5)) if roes else None
|
| 307 |
|
| 308 |
d = {
|
| 309 |
-
"sym": symbol
|
| 310 |
"shares": shares, "sector": info.get("sector") or "Unknown", "industry": industry, "seg": seg,
|
| 311 |
"bvps": bvps, "eps": eps, "dy": dy, "s": s, "flags": flags, "unit_ok": unit_ok, "stmt_date": sd, "age": age,
|
| 312 |
"tax_rate": tr, "tax_assumed": tax_assumed, "net_debt": net_debt, "fcff": fcff, "fcf0": fcf0, "oe": oe,
|
|
@@ -398,7 +408,7 @@ def relative_value(d, med) -> Tuple[Optional[float], str]:
|
|
| 398 |
seg, vals = d["seg"], []
|
| 399 |
if m["pb"][0] and d["bvps"] and d["bvps"] > 0:
|
| 400 |
vals.append(m["pb"][0] * d["bvps"])
|
| 401 |
-
if seg != "
|
| 402 |
vals.append(m["pe"][0] * d["eps"])
|
| 403 |
if seg == "Industrial" and m["ev_ebitda"][0] and (d["s"]["ebitda"][0] or 0) > 0 and d["net_debt"] is not None and d["shares"]:
|
| 404 |
vals.append((m["ev_ebitda"][0] * d["s"]["ebitda"][0] - d["net_debt"]) / d["shares"])
|
|
@@ -419,7 +429,7 @@ def intrinsic(d, P, med, rg=None, rw=None):
|
|
| 419 |
else:
|
| 420 |
m["Justified P/B"] = (None, "ROE/book value unavailable")
|
| 421 |
m["DCF"] = (None, "not applied to banks/insurers")
|
| 422 |
-
elif seg == "
|
| 423 |
m["NAV proxy (book)"] = ((bv, "book value; appraised NAV not in Yahoo") if (d["unit_ok"] and bv and bv > 0) else (None, "book value unavailable"))
|
| 424 |
else:
|
| 425 |
m["DCF"] = dcf_try(d["fcff"], g0, wacc, tg, d["shares"], d["net_debt"], d["unit_ok"])
|
|
@@ -591,7 +601,7 @@ def risk_engine(d, q, cov):
|
|
| 591 |
# ============================================================================
|
| 592 |
|
| 593 |
WEIGHTS = {"Industrial": (.35, .30, .20, .15), "Bank": (.40, .35, .10, .15),
|
| 594 |
-
"Insurance": (.40, .30, .15, .15), "
|
| 595 |
|
| 596 |
|
| 597 |
def coverage(d):
|
|
@@ -622,7 +632,7 @@ def analyze(d, P, med, probs=(0.25, 0.5, 0.25)):
|
|
| 622 |
vi = [(lin(A["mos"], -0.3, 0.5), 4), (lin(A["rel_mos"], -0.3, 0.5), 2)]
|
| 623 |
if bank:
|
| 624 |
vi += [(pe_s, 2), (pb_s, 3), (lin(d["dy"], 0, 0.08), 1)]
|
| 625 |
-
elif seg == "
|
| 626 |
vi += [(lin(d["pb"], 1.5, 0.4), 3), (pe_s, 1)]
|
| 627 |
else:
|
| 628 |
vi += [(pe_s, 1), (pb_s, 1), (lin(d["ev_ebitda"], 15, 3), 1), (lin(d["fcf_yield"], -0.02, 0.12), 1),
|
|
@@ -711,7 +721,7 @@ def scan_row(A):
|
|
| 711 |
|
| 712 |
|
| 713 |
def run_scan(force, extra, P, progress):
|
| 714 |
-
syms =
|
| 715 |
syms = list(dict.fromkeys(syms))
|
| 716 |
ds, failed = [], []
|
| 717 |
for i, s in enumerate(syms):
|
|
@@ -739,7 +749,7 @@ def apply_filters(seg, minc, minm, sortby, desc):
|
|
| 739 |
if minm > -100:
|
| 740 |
x = x[x["MoS %"].fillna(-1e9) >= minm]
|
| 741 |
x = x.sort_values(sortby, ascending=not desc, na_position="last")
|
| 742 |
-
f = tempfile.NamedTemporaryFile(delete=False, suffix=".csv", prefix="
|
| 743 |
x.to_csv(f.name, index=False)
|
| 744 |
return x, f.name
|
| 745 |
|
|
@@ -747,12 +757,12 @@ def apply_filters(seg, minc, minm, sortby, desc):
|
|
| 747 |
def md_report(A, P):
|
| 748 |
d = A["d"]
|
| 749 |
L = [f"## {d['sym']} — {d['name']}",
|
| 750 |
-
f"**{d['seg']}** · {d['sector']} / {d['industry']} · Fiyat **{fm(d['price'])}
|
| 751 |
f"{d['stmt_date'].date() if d['stmt_date'] else 'N/A'}",
|
| 752 |
f"**Composite {fm(A['composite'], 0)}** | Valuation {fm(A['val'], 0)} | Quality {fm(A['qual'], 0)} | "
|
| 753 |
f"Strength {fm(A['str'], 0)} | Risk {fm(A['risk'], 0)} (düşük=iyi) | Coverage {fp(A['cov'], 0)} | "
|
| 754 |
f"Confidence **{A['conf']}**",
|
| 755 |
-
f"**Intrinsic value [D]:** {fm(A['iv'])}
|
| 756 |
L.append("\n| Yöntem | Değer/hisse | Not |\n|---|---|---|")
|
| 757 |
for k, (v, n) in A["methods"].items():
|
| 758 |
L.append(f"| {k} | {fm(v)} | {n} |")
|
|
@@ -808,8 +818,8 @@ def self_test() -> str:
|
|
| 808 |
chk("Sector median needs >=5 peers", build_medians(ds)[("Industrial", "X")]["pe"][0] is None)
|
| 809 |
ds += [{"seg": "Industrial", "sector": "X", "pe": 20.0, **{m: None for m in MULTS if m != "pe"}}]
|
| 810 |
chk("Sector median with 5 peers computed", build_medians(ds)[("Industrial", "X")]["pe"][0] == 12.0 or build_medians(ds)[("Industrial", "X")]["pe"][0] is not None)
|
| 811 |
-
chk("Segment classification", segment_of("Banks - Regional", "
|
| 812 |
-
and segment_of("Insurance - Life", "
|
| 813 |
return "\n".join(res)
|
| 814 |
|
| 815 |
|
|
@@ -818,14 +828,14 @@ def self_test() -> str:
|
|
| 818 |
# ============================================================================
|
| 819 |
|
| 820 |
def build_ui():
|
| 821 |
-
with gr.Blocks(title="
|
| 822 |
-
gr.Markdown("#
|
| 823 |
"Eksik veri `N/A` gösterilir, asla sıfırla doldurulmaz. 12 aylık senaryolar heuristiktir, istatistiksel tahmin değildir. "
|
| 824 |
"Yatırım tavsiyesi değildir.**")
|
| 825 |
with gr.Accordion("⚙️ Varsayımlar [A] (tüm sekmeler için)", open=False):
|
| 826 |
with gr.Row():
|
| 827 |
-
s_infl = gr.Slider(0,
|
| 828 |
-
s_rw = gr.Slider(1,
|
| 829 |
s_rtg = gr.Slider(0, 5, 1, step=0.25, label="Reel terminal büyüme (%)")
|
| 830 |
with gr.Row():
|
| 831 |
s_rg = gr.Slider(-10, 20, 3, step=0.5, label="Baz reel büyüme (%)")
|
|
@@ -838,9 +848,9 @@ def build_ui():
|
|
| 838 |
with gr.Row():
|
| 839 |
scan_btn = gr.Button("Run Scanner", variant="primary", scale=2)
|
| 840 |
force = gr.Checkbox(label="Force refresh", value=False)
|
| 841 |
-
extra = gr.Textbox(label="Ek tickerlar (virgülle, örn:
|
| 842 |
with gr.Row():
|
| 843 |
-
f_seg = gr.Dropdown(["All", "Industrial", "Bank", "Insurance", "
|
| 844 |
f_c = gr.Slider(0, 100, 0, step=5, label="Min composite")
|
| 845 |
f_m = gr.Slider(-100, 100, -100, step=5, label="Min MoS % (-100 = kapalı)")
|
| 846 |
with gr.Row():
|
|
@@ -1080,12 +1090,12 @@ def build_ui():
|
|
| 1080 |
|
| 1081 |
def dq(progress=gr.Progress()):
|
| 1082 |
rows, failed = [], []
|
| 1083 |
-
for i, s in enumerate(
|
| 1084 |
-
progress((i + 1) / len(
|
| 1085 |
was = _cget(f"t_{s}") is not None
|
| 1086 |
d = fetch(s)
|
| 1087 |
if d is None:
|
| 1088 |
-
failed.append(s
|
| 1089 |
continue
|
| 1090 |
c = coverage(d)
|
| 1091 |
rows.append({"Ticker": d["sym"], "Segment": d["seg"], "Coverage %": round(c * 100),
|
|
@@ -1111,10 +1121,10 @@ def build_ui():
|
|
| 1111 |
|
| 1112 |
METHODOLOGY = """
|
| 1113 |
### Değerleme mimarisi
|
| 1114 |
-
- **DCF (sanayi/
|
| 1115 |
- **Owner Earnings:** NI + D&A − bakım capex + ΔİSermayesi. Bakım capex = min(toplam capex, D&A) **tahminidir**; veri eksikse yöntem kullanılmaz. Özsermaye nakit akışı olduğu için net borç düşülmez.
|
| 1116 |
- **Banka/Sigorta:** Gordon gerekçeli P/B = (sürdürülebilir ROE − g)/(COE − g) × defter değeri. FCF/EV-EBITDA kullanılmaz. Sermaye yeterliliği/aktif kalitesi Yahoo'da yok.
|
| 1117 |
-
- **
|
| 1118 |
- **Relative:** sektör medyanı (en az 5 geçerli peer) P/E, P/B, EV/EBITDA → hisse başına değer.
|
| 1119 |
- **Intrinsic = mevcut yöntemlerin medyanı.** MoS = (IV − fiyat)/IV.
|
| 1120 |
- **Reverse DCF:** fiyatı veren yıl-1 FCF büyümesi (bisection), iskonto oranı ±3 puan duyarlılığı.
|
|
@@ -1128,14 +1138,14 @@ Composite = ağırlıklı ortalama (mevcut bileşenler üzerinden yeniden normal
|
|
| 1128 |
| Sanayi | 35 | 30 | 20 | 15 |
|
| 1129 |
| Banka | 40 | 35 | 10 | 15 |
|
| 1130 |
| Sigorta | 40 | 30 | 15 | 15 |
|
| 1131 |
-
|
|
| 1132 |
|
| 1133 |
Risk (0-100): 10 bayrak (distress, kaldıraç, likidite, negatif FCF, kâr/marj bozulması, CFO-NI ayrışması, seyrelme, aşırı değerleme, veri güvenilirliği); yalnızca değerlendirilebilen bayrakların ağırlığı üzerinden hesaplanır. Piotroski 9 bileşenli; hesaplanamayan bileşen "N/A" olur ve skor `x/n` olarak gösterilir.
|
| 1134 |
|
| 1135 |
### Bilinen veri sınırlamaları
|
| 1136 |
- yfinance point-in-time değildir; backtest geçersizdir. Bilanço tarihleri ve kapsam hisseden hisseye değişir.
|
| 1137 |
-
- Yıllık tablolar gecikmelidir;
|
| 1138 |
-
- Evren: gömülü liste + eklediğiniz tickerlar (tüm
|
| 1139 |
- Sektör/segment sınıflaması Yahoo `industry` alanına dayanır; peer sayısı <5 ise medyan hesaplanmaz.
|
| 1140 |
- Sonuçlar araştırma/eğitim amaçlıdır, yatırım tavsiyesi değildir.
|
| 1141 |
"""
|
|
|
|
| 1 |
"""
|
| 2 |
+
US Value Intelligence v3 — Compact Value & Intrinsic Valuation Platform
|
| 3 |
=========================================================================
|
| 4 |
Data integrity > model complexity. Intrinsic value > momentum. Margin of safety > prediction.
|
| 5 |
|
|
|
|
| 20 |
import yfinance as yf
|
| 21 |
|
| 22 |
logging.basicConfig(level=logging.INFO, format="%(asctime)s | %(levelname)s | %(message)s")
|
| 23 |
+
logger = logging.getLogger("us_v3")
|
| 24 |
+
|
| 25 |
+
US_UNIVERSE = [
|
| 26 |
+
# Teknoloji
|
| 27 |
+
"AAPL", "MSFT", "NVDA", "GOOGL", "META", "AVGO", "ORCL", "ADBE", "CRM", "CSCO",
|
| 28 |
+
"INTC", "AMD", "QCOM", "TXN", "IBM", "INTU", "AMAT", "MU", "NOW", "ACN",
|
| 29 |
+
# Tüketici
|
| 30 |
+
"AMZN", "TSLA", "HD", "MCD", "NKE", "SBUX", "LOW", "TJX", "COST", "WMT",
|
| 31 |
+
"PG", "KO", "PEP", "PM", "MO", "MDLZ", "CL", "TGT",
|
| 32 |
+
# Sağlık
|
| 33 |
+
"UNH", "JNJ", "LLY", "PFE", "MRK", "ABBV", "TMO", "ABT", "DHR", "BMY",
|
| 34 |
+
"AMGN", "GILD", "MDT", "CVS", "ISRG",
|
| 35 |
+
# Bankalar
|
| 36 |
+
"JPM", "BAC", "WFC", "C", "GS", "MS", "USB", "PNC", "TFC", "SCHW", "BK",
|
| 37 |
+
# Sigorta
|
| 38 |
+
"BRK-B", "PGR", "CB", "MMC", "AIG", "MET", "PRU", "ALL", "TRV", "AFL",
|
| 39 |
+
# Sanayi
|
| 40 |
+
"CAT", "DE", "HON", "GE", "UPS", "UNP", "RTX", "LMT", "BA", "MMM",
|
| 41 |
+
"FDX", "EMR", "ETN", "ITW", "WM",
|
| 42 |
+
# Enerji
|
| 43 |
+
"XOM", "CVX", "COP", "SLB", "EOG", "OXY", "PSX", "MPC", "KMI",
|
| 44 |
+
# Kamu hizmetleri
|
| 45 |
+
"NEE", "DUK", "SO", "D", "AEP",
|
| 46 |
+
# Malzeme
|
| 47 |
+
"LIN", "SHW", "APD", "NEM", "FCX", "DOW", "NUE",
|
| 48 |
+
# REIT
|
| 49 |
+
"PLD", "AMT", "EQIX", "O", "SPG", "PSA", "WELL", "DLR", "CCI",
|
| 50 |
+
# İletişim / medya
|
| 51 |
+
"T", "VZ", "CMCSA", "DIS", "NFLX", "TMUS",
|
| 52 |
]
|
| 53 |
+
US_UNIVERSE = list(dict.fromkeys(US_UNIVERSE)) # tekrarları temizle
|
| 54 |
+
assert len(US_UNIVERSE) >= 100, f"Evren {len(US_UNIVERSE)} hisse; en az 100 olmalı"
|
|
|
|
| 55 |
|
| 56 |
REQUEST_DELAY_SEC = 0.3
|
| 57 |
CACHE_TTL_HOURS = 6
|
|
|
|
| 134 |
|
| 135 |
|
| 136 |
def norm_sym(t: str) -> str:
|
| 137 |
+
t = (t or "").strip().upper().replace(".", "-") # Yahoo: BRK.B -> BRK-B
|
| 138 |
+
return t
|
| 139 |
|
| 140 |
|
| 141 |
def mk_params(infl, rw, rtg, rg, pt, cv) -> Dict[str, float]:
|
|
|
|
| 189 |
return "Bank"
|
| 190 |
if "insurance" in ind:
|
| 191 |
return "Insurance"
|
| 192 |
+
if "reit" in ind or "real estate" in ind:
|
| 193 |
+
return "REIT"
|
| 194 |
return "Industrial"
|
| 195 |
|
| 196 |
|
|
|
|
| 307 |
ev = (mcap + net_debt) if (mcap and net_debt is not None) else num(info.get("enterpriseValue"))
|
| 308 |
ebitda0 = s["ebitda"][0]
|
| 309 |
industry = info.get("industry") or "Unknown"
|
| 310 |
+
seg = segment_of(industry, symbol)
|
| 311 |
roes = [div(s["ni"][i], s["eq"][i]) for i in range(3) if (s["eq"][i] or 0) > 0]
|
| 312 |
roes = [r for r in roes if r is not None]
|
| 313 |
ir = num(info.get("returnOnEquity"))
|
|
|
|
| 316 |
sust_roe = float(np.clip(np.mean(roes), -1, 1.5)) if roes else None
|
| 317 |
|
| 318 |
d = {
|
| 319 |
+
"sym": symbol, "name": info.get("shortName") or symbol, "price": price, "mcap": mcap,
|
| 320 |
"shares": shares, "sector": info.get("sector") or "Unknown", "industry": industry, "seg": seg,
|
| 321 |
"bvps": bvps, "eps": eps, "dy": dy, "s": s, "flags": flags, "unit_ok": unit_ok, "stmt_date": sd, "age": age,
|
| 322 |
"tax_rate": tr, "tax_assumed": tax_assumed, "net_debt": net_debt, "fcff": fcff, "fcf0": fcf0, "oe": oe,
|
|
|
|
| 408 |
seg, vals = d["seg"], []
|
| 409 |
if m["pb"][0] and d["bvps"] and d["bvps"] > 0:
|
| 410 |
vals.append(m["pb"][0] * d["bvps"])
|
| 411 |
+
if seg != "REIT" and m["pe"][0] and d["eps"] and d["eps"] > 0:
|
| 412 |
vals.append(m["pe"][0] * d["eps"])
|
| 413 |
if seg == "Industrial" and m["ev_ebitda"][0] and (d["s"]["ebitda"][0] or 0) > 0 and d["net_debt"] is not None and d["shares"]:
|
| 414 |
vals.append((m["ev_ebitda"][0] * d["s"]["ebitda"][0] - d["net_debt"]) / d["shares"])
|
|
|
|
| 429 |
else:
|
| 430 |
m["Justified P/B"] = (None, "ROE/book value unavailable")
|
| 431 |
m["DCF"] = (None, "not applied to banks/insurers")
|
| 432 |
+
elif seg == "REIT":
|
| 433 |
m["NAV proxy (book)"] = ((bv, "book value; appraised NAV not in Yahoo") if (d["unit_ok"] and bv and bv > 0) else (None, "book value unavailable"))
|
| 434 |
else:
|
| 435 |
m["DCF"] = dcf_try(d["fcff"], g0, wacc, tg, d["shares"], d["net_debt"], d["unit_ok"])
|
|
|
|
| 601 |
# ============================================================================
|
| 602 |
|
| 603 |
WEIGHTS = {"Industrial": (.35, .30, .20, .15), "Bank": (.40, .35, .10, .15),
|
| 604 |
+
"Insurance": (.40, .30, .15, .15), "REIT": (.45, .20, .20, .15)}
|
| 605 |
|
| 606 |
|
| 607 |
def coverage(d):
|
|
|
|
| 632 |
vi = [(lin(A["mos"], -0.3, 0.5), 4), (lin(A["rel_mos"], -0.3, 0.5), 2)]
|
| 633 |
if bank:
|
| 634 |
vi += [(pe_s, 2), (pb_s, 3), (lin(d["dy"], 0, 0.08), 1)]
|
| 635 |
+
elif seg == "REIT":
|
| 636 |
vi += [(lin(d["pb"], 1.5, 0.4), 3), (pe_s, 1)]
|
| 637 |
else:
|
| 638 |
vi += [(pe_s, 1), (pb_s, 1), (lin(d["ev_ebitda"], 15, 3), 1), (lin(d["fcf_yield"], -0.02, 0.12), 1),
|
|
|
|
| 721 |
|
| 722 |
|
| 723 |
def run_scan(force, extra, P, progress):
|
| 724 |
+
syms = US_UNIVERSE + [norm_sym(x) for x in (extra or "").split(",") if x.strip()]
|
| 725 |
syms = list(dict.fromkeys(syms))
|
| 726 |
ds, failed = [], []
|
| 727 |
for i, s in enumerate(syms):
|
|
|
|
| 749 |
if minm > -100:
|
| 750 |
x = x[x["MoS %"].fillna(-1e9) >= minm]
|
| 751 |
x = x.sort_values(sortby, ascending=not desc, na_position="last")
|
| 752 |
+
f = tempfile.NamedTemporaryFile(delete=False, suffix=".csv", prefix="us_v3_")
|
| 753 |
x.to_csv(f.name, index=False)
|
| 754 |
return x, f.name
|
| 755 |
|
|
|
|
| 757 |
def md_report(A, P):
|
| 758 |
d = A["d"]
|
| 759 |
L = [f"## {d['sym']} — {d['name']}",
|
| 760 |
+
f"**{d['seg']}** · {d['sector']} / {d['industry']} · Fiyat **{fm(d['price'])} USD** · Mali tablo: "
|
| 761 |
f"{d['stmt_date'].date() if d['stmt_date'] else 'N/A'}",
|
| 762 |
f"**Composite {fm(A['composite'], 0)}** | Valuation {fm(A['val'], 0)} | Quality {fm(A['qual'], 0)} | "
|
| 763 |
f"Strength {fm(A['str'], 0)} | Risk {fm(A['risk'], 0)} (düşük=iyi) | Coverage {fp(A['cov'], 0)} | "
|
| 764 |
f"Confidence **{A['conf']}**",
|
| 765 |
+
f"**Intrinsic value [D]:** {fm(A['iv'])} USD · Margin of Safety {fp(A['mos'])}"]
|
| 766 |
L.append("\n| Yöntem | Değer/hisse | Not |\n|---|---|---|")
|
| 767 |
for k, (v, n) in A["methods"].items():
|
| 768 |
L.append(f"| {k} | {fm(v)} | {n} |")
|
|
|
|
| 818 |
chk("Sector median needs >=5 peers", build_medians(ds)[("Industrial", "X")]["pe"][0] is None)
|
| 819 |
ds += [{"seg": "Industrial", "sector": "X", "pe": 20.0, **{m: None for m in MULTS if m != "pe"}}]
|
| 820 |
chk("Sector median with 5 peers computed", build_medians(ds)[("Industrial", "X")]["pe"][0] == 12.0 or build_medians(ds)[("Industrial", "X")]["pe"][0] is not None)
|
| 821 |
+
chk("Segment classification", segment_of("Banks - Regional", "JPM") == "Bank" and segment_of("REIT - Industrial", "PLD") == "REIT"
|
| 822 |
+
and segment_of("Insurance - Life", "MET") == "Insurance" and segment_of("Steel", "NUE") == "Industrial")
|
| 823 |
return "\n".join(res)
|
| 824 |
|
| 825 |
|
|
|
|
| 828 |
# ============================================================================
|
| 829 |
|
| 830 |
def build_ui():
|
| 831 |
+
with gr.Blocks(title="US Value Intelligence v3", theme=gr.themes.Soft()) as demo:
|
| 832 |
+
gr.Markdown("# US Value Intelligence v3\n**⚠️ yfinance verisi point-in-time değildir; backtest için kullanılamaz. "
|
| 833 |
"Eksik veri `N/A` gösterilir, asla sıfırla doldurulmaz. 12 aylık senaryolar heuristiktir, istatistiksel tahmin değildir. "
|
| 834 |
"Yatırım tavsiyesi değildir.**")
|
| 835 |
with gr.Accordion("⚙️ Varsayımlar [A] (tüm sekmeler için)", open=False):
|
| 836 |
with gr.Row():
|
| 837 |
+
s_infl = gr.Slider(0, 20, 3, step=0.5, label="Enflasyon (%)")
|
| 838 |
+
s_rw = gr.Slider(1, 20, 7, step=0.5, label="Reel iskonto oranı (%)")
|
| 839 |
s_rtg = gr.Slider(0, 5, 1, step=0.25, label="Reel terminal büyüme (%)")
|
| 840 |
with gr.Row():
|
| 841 |
s_rg = gr.Slider(-10, 20, 3, step=0.5, label="Baz reel büyüme (%)")
|
|
|
|
| 848 |
with gr.Row():
|
| 849 |
scan_btn = gr.Button("Run Scanner", variant="primary", scale=2)
|
| 850 |
force = gr.Checkbox(label="Force refresh", value=False)
|
| 851 |
+
extra = gr.Textbox(label="Ek tickerlar (virgülle, örn: BRK-B,AAPL)", value="")
|
| 852 |
with gr.Row():
|
| 853 |
+
f_seg = gr.Dropdown(["All", "Industrial", "Bank", "Insurance", "REIT"], value="All", label="Segment")
|
| 854 |
f_c = gr.Slider(0, 100, 0, step=5, label="Min composite")
|
| 855 |
f_m = gr.Slider(-100, 100, -100, step=5, label="Min MoS % (-100 = kapalı)")
|
| 856 |
with gr.Row():
|
|
|
|
| 1090 |
|
| 1091 |
def dq(progress=gr.Progress()):
|
| 1092 |
rows, failed = [], []
|
| 1093 |
+
for i, s in enumerate(US_UNIVERSE):
|
| 1094 |
+
progress((i + 1) / len(US_UNIVERSE), desc=f"Checking {s}")
|
| 1095 |
was = _cget(f"t_{s}") is not None
|
| 1096 |
d = fetch(s)
|
| 1097 |
if d is None:
|
| 1098 |
+
failed.append(s)
|
| 1099 |
continue
|
| 1100 |
c = coverage(d)
|
| 1101 |
rows.append({"Ticker": d["sym"], "Segment": d["seg"], "Coverage %": round(c * 100),
|
|
|
|
| 1121 |
|
| 1122 |
METHODOLOGY = """
|
| 1123 |
### Değerleme mimarisi
|
| 1124 |
+
- **DCF (sanayi/REIT dışı):** FCFF = FCF + faiz×(1−vergi) (Yahoo FCF'si faiz sonrası olduğu için borç çift sayılmasın diye). 5 yıl, büyüme yıl-1'den terminale doğrusal sönümlenir; EV − net borç = özsermaye; / hisse. Girdi eksik, FCFF ≤ 0 veya iskonto ≤ terminal büyüme ise **hesaplanmaz**.
|
| 1125 |
- **Owner Earnings:** NI + D&A − bakım capex + ΔİSermayesi. Bakım capex = min(toplam capex, D&A) **tahminidir**; veri eksikse yöntem kullanılmaz. Özsermaye nakit akışı olduğu için net borç düşülmez.
|
| 1126 |
- **Banka/Sigorta:** Gordon gerekçeli P/B = (sürdürülebilir ROE − g)/(COE − g) × defter değeri. FCF/EV-EBITDA kullanılmaz. Sermaye yeterliliği/aktif kalitesi Yahoo'da yok.
|
| 1127 |
+
- **REIT:** NAV vekili = defter değeri (ekspertiz NAV'ı yok), P/NAV. Doluluk verisi yok.
|
| 1128 |
- **Relative:** sektör medyanı (en az 5 geçerli peer) P/E, P/B, EV/EBITDA → hisse başına değer.
|
| 1129 |
- **Intrinsic = mevcut yöntemlerin medyanı.** MoS = (IV − fiyat)/IV.
|
| 1130 |
- **Reverse DCF:** fiyatı veren yıl-1 FCF büyümesi (bisection), iskonto oranı ±3 puan duyarlılığı.
|
|
|
|
| 1138 |
| Sanayi | 35 | 30 | 20 | 15 |
|
| 1139 |
| Banka | 40 | 35 | 10 | 15 |
|
| 1140 |
| Sigorta | 40 | 30 | 15 | 15 |
|
| 1141 |
+
| REIT | 45 | 20 | 20 | 15 |
|
| 1142 |
|
| 1143 |
Risk (0-100): 10 bayrak (distress, kaldıraç, likidite, negatif FCF, kâr/marj bozulması, CFO-NI ayrışması, seyrelme, aşırı değerleme, veri güvenilirliği); yalnızca değerlendirilebilen bayrakların ağırlığı üzerinden hesaplanır. Piotroski 9 bileşenli; hesaplanamayan bileşen "N/A" olur ve skor `x/n` olarak gösterilir.
|
| 1144 |
|
| 1145 |
### Bilinen veri sınırlamaları
|
| 1146 |
- yfinance point-in-time değildir; backtest geçersizdir. Bilanço tarihleri ve kapsam hisseden hisseye değişir.
|
| 1147 |
+
- Yıllık tablolar gecikmelidir; Yıllık tablolar GAAP/ADR farklarına ve tek seferlik kalemlere duyarlıdır; yabancı (ADR) şirketlerde para birimi uyuşmazlığı bayraklanır.
|
| 1148 |
+
- Evren: gömülü liste + eklediğiniz tickerlar (tüm ABD piyasası Yahoo'dan ücretsiz çekilemez).
|
| 1149 |
- Sektör/segment sınıflaması Yahoo `industry` alanına dayanır; peer sayısı <5 ise medyan hesaplanmaz.
|
| 1150 |
- Sonuçlar araştırma/eğitim amaçlıdır, yatırım tavsiyesi değildir.
|
| 1151 |
"""
|