GoshawkVortexAI commited on
Commit
12dc8ff
·
verified ·
1 Parent(s): c730e75

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +62 -52
app.py CHANGED
@@ -1,5 +1,5 @@
1
  """
2
- BIST Value Intelligence v3 — Compact Value & Intrinsic Valuation Platform
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("bist_v3")
24
-
25
- BIST_UNIVERSE = [
26
- # Mevcut çekirdek liste
27
- "AEFES", "AKBNK", "AKSA", "AKSEN", "ALARK", "ARCLK", "ASELS", "ASTOR", "BIMAS", "BRSAN",
28
- "CCOLA", "CIMSA", "DOAS", "DOHOL", "ECILC", "EKGYO", "ENJSA", "ENKAI", "EREGL", "FROTO",
29
- "GARAN", "GUBRF", "HEKTS", "ISCTR", "KCHOL", "KRDMD", "MGROS", "ODAS", "PETKM", "PGSUS",
30
- "SAHOL", "SASA", "SISE", "SKBNK", "TCELL", "THYAO", "TKFEN", "TOASO", "TSKB", "TTKOM",
31
- "TUPRS", "ULKER", "VAKBN", "VESBE", "YKBNK", "ZOREN", "ISGYO", "TRGYO", "HALKB", "AKGRT",
32
- "ANSGR", "TURSG", "ANHYT", "RAYSG",
33
- # Genişletme (>=100 hisse için)
34
- "AGHOL", "AHGAZ", "ALBRK", "ALFAS", "ARDYZ", "AYDEM", "BAGFS", "BERA", "BIOEN", "BRISA",
35
- "BRYAT", "BTCIM", "BUCIM", "CANTE", "CWENE", "EGEEN", "ENERY", "EUPWR", "GENIL", "GESAN",
36
- "GLYHO", "GWIND", "IPEKE", "ISDMR", "ISMEN", "KARSN", "KCAER", "KLSER", "KONTR", "KONYA",
37
- "KOZAA", "KOZAL", "KTLEV", "LOGO", "MAVI", "MIATK", "MPARK", "OTKAR", "OYAKC", "PSGYO",
38
- "QUAGR", "REEDR", "SELEC", "SMRTG", "SNGYO", "SOKM", "TABGD", "TAVHL", "TMSN", "TTRAK",
39
- "TKNSA", "TUKAS", "ULUUN", "VAKKO", "VESTL", "YEOTK", "ZRGYO", "ECZYT", "EBEBK", "FENER",
40
- "CLEBI", "DOCO", "EUREN", "INVEO", "KMPUR", "LMKDC", "NTHOL", "ONCSM", "PASEU", "TRMET",
 
 
 
 
 
 
 
 
 
 
 
41
  ]
42
- BIST_UNIVERSE = list(dict.fromkeys(BIST_UNIVERSE)) # tekrarları temizle
43
- assert len(BIST_UNIVERSE) >= 100, f"Evren {len(BIST_UNIVERSE)} hisse; en az 100 olmalı"
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 if t.endswith(".IS") else t + ".IS"
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 or sym.upper().endswith("GYO"):
183
- return "GYO"
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.replace(".IS", ""))
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.replace(".IS", ""), "name": info.get("shortName") or symbol, "price": price, "mcap": mcap,
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 != "GYO" and m["pe"][0] and d["eps"] and d["eps"] > 0:
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 == "GYO":
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), "GYO": (.45, .20, .20, .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 == "GYO":
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 = BIST_UNIVERSE + [norm_sym(x) for x in (extra or "").split(",") if x.strip()]
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="bist_v3_")
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'])} TRY** · Mali tablo: "
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'])} TRY · Margin of Safety {fp(A['mos'])}"]
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", "GARAN") == "Bank" and segment_of("Real Estate", "EKGYO") == "GYO"
812
- and segment_of("Insurance - Life", "ANHYT") == "Insurance" and segment_of("Steel", "EREGL") == "Industrial")
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="BIST Value Intelligence v3", theme=gr.themes.Soft()) as demo:
822
- gr.Markdown("# BIST Value Intelligence v3\n**⚠️ yfinance verisi point-in-time değildir; backtest için kullanılamaz. "
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, 100, 30, step=1, label="Enflasyon (%)")
828
- s_rw = gr.Slider(1, 30, 10, step=0.5, label="Reel iskonto oranı (%)")
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: TRGYO,KONTR)", value="")
842
  with gr.Row():
843
- f_seg = gr.Dropdown(["All", "Industrial", "Bank", "Insurance", "GYO"], value="All", label="Segment")
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(BIST_UNIVERSE):
1084
- progress((i + 1) / len(BIST_UNIVERSE), desc=f"Checking {s}")
1085
  was = _cget(f"t_{s}") is not None
1086
  d = fetch(s)
1087
  if d is None:
1088
- failed.append(s.replace(".IS", ""))
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/GYO 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**.
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
- - **GYO:** NAV vekili = defter değeri (ekspertiz NAV'ı yok), P/NAV. Doluluk verisi yok.
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
- | GYO | 45 | 20 | 20 | 15 |
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; TFRS/enflasyon muhasebesi (TMS 29) etkileri nominal rakamları çarpıtabilir.
1138
- - Evren: gömülü liste + eklediğiniz tickerlar (tüm BIST listesi Yahoo'dan ücretsiz çekilemez).
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
  """