diff --git "a/app.py" "b/app.py" deleted file mode 100644--- "a/app.py" +++ /dev/null @@ -1,2916 +0,0 @@ -""" -OKX QUANT TERMINAL · v4 — KNN ANA MOTOR + SMC / VWAP TEYİTLERİ -Kripto piyasa analitiği paneli (Gradio). - -Yalnızca OKX'in herkese açık (anahtarsız) piyasa verisi REST API'si kullanılır: - - /api/v5/market/candles, /ticker, /tickers, /books - - /api/v5/public/funding-rate (türev duyarlılığı, isteğe bağlı bilgi) - -API anahtarı yok, emir gönderimi yok, hesap erişimi yok. Salt-okunur analiz / sinyal terminalidir. - -v4 — KNN ANA MOTOR + SMC / VWAP TEYİTLERİ - * YÖN = KNN: şimdiki piyasa durumuna (13 özellik: RSI, Stokastik, ADX, EMA/VWAP sapması, momentum, hacim, - premium/discount, piyasa yapısı) en benzer K geçmiş bar bulunur; sonucu BİLİNEN komşuların (5 bar sonra - yükseldi / düştü) çoğunluğu yönü belirler. Havuza yalnızca etiketi o ana kadar bilinen barlar girer (nedensel). - * KNN puanı: komşu payı farkı (edge) ≥%15 → 1, ≥%30 → 2, ≥%50 → 3, ≥%70 → 4 puan. Edge %15'in altındaysa KNN - kararsızdır; bu durumda SMC/VWAP + piyasa yapısı filtresi devralır (KNN_NEUTRAL_FALLBACK). - * TEYİTLER (SMC + VWAP + XGBoost, her biri ±1): taze BOS/CHoCH · premium/discount · order block · FVG retest · likidite avı · - Acheron VWAP etkileşimi · XGBoost (KNN ile aynı özellikler, walk-forward eğitilmiş P(yükseliş)). KNN yönündekiler skoru artırır, ters olanlar azaltır; teyitler yönü ÇEVİREMEZ. - Skor = KNN puanı + net teyit (±11). AL/SAT: |skor| ≥ 2, GÜÇLÜ: |skor| ≥ 4 + uyum + üst zaman dilimi yapısı uyumu. - * DİNAMİK SL/TP: KNN komşularının sinyalden sonraki DYN_HORIZON barda GERÇEKLEŞMİŞ lehte/aleyhte sapmalarından - (MFE/MAE, ATR cinsinden) türetilir: SL = aleyhte sapmanın %75'i, TP1/TP2/TP3 = lehte sapmanın %50/%75/%90'ı. - Yalnızca sonucu o ana kadar bilinen komşular kullanılır (nedensel). Komşu yetersizse sabit SL_ATR/TP_ATR. - * Yedek mod: KNN havuzu yetersizse (az mum) eski SMC kuralı devreye girer (yapı filtreli net teyit sayısı). - * Üst zaman dilimi (MTF), emir defteri, funding, S/R: yalnızca BİLGİ / filtre — skora puan eklemez. - * LLM katmanı kaldırıldı. Fiyat grafikleri 3D (Plotly Scatter3d / Mesh3d / Surface). - * Güven skoru bileşenlerin eşit ağırlıklı ortalamasıdır. Stop: order block / likidite avı ekstremi arkası. - * Gerçek işlem simülasyonu (SL/TP/zaman çıkışı + komisyon): kazanma oranı, profit factor, beklenti, R, SQN, - drawdown, Al&Tut karşılaştırması. Canlı sinyal ve backtest AYNI nedensel fonksiyonu kullanır. - * RSI / MACD / Stokastik / ADX grafikleri bilgi amaçlıdır; sinyale dahil değildir. - * RİSK MOTORU (yalnızca Prophet): risk = kullanıcı riski × Prophet çarpanı. Kelly / CVaR / drawdown / P-hit katmanları KALDIRILDI. - Prophet (Meta) fiyat trendini ve volatiliteyi tahmin eder; sinyale ters trend → VETO, volatilite artışı bekleniyorsa pozisyon küçülür. Yalnızca risk azaltır, yön üretmez. -""" -import json -import math -import os -import threading -import time -from concurrent.futures import ThreadPoolExecutor, as_completed - -import numpy as np -import pandas as pd -import plotly.graph_objects as go -from plotly.subplots import make_subplots -import requests -import gradio as gr -import logging -from html import escape as _html_escape -from statistics import NormalDist -try: # Meta (Facebook) Prophet — isteğe bağlı: kurulu değilse risk motoru Prophet katmanı olmadan çalışır (pip install prophet) - from prophet import Prophet - for _n in ("prophet", "cmdstanpy"): - logging.getLogger(_n).setLevel(logging.WARNING) -except Exception as _pe: - Prophet = None - print(f"[Prophet] 'prophet' paketi yüklenemedi ({str(_pe)[:100]}) → yerleşik Prophet-lite yedeği kullanılacak. Gerçek Prophet için: pip install prophet") - -try: # XGBoost — isteğe bağlı: kurulu değilse scikit-learn gradient boosting yedeği, o da yoksa katman kapalı (pip install xgboost) - from xgboost import XGBClassifier - _SkGB = None -except Exception as _xe: - XGBClassifier = None - try: - from sklearn.ensemble import HistGradientBoostingClassifier as _SkGB - print(f"[XGBoost] 'xgboost' paketi yüklenemedi ({str(_xe)[:80]}) → scikit-learn gradient boosting yedeği kullanılacak. Gerçek XGBoost için: pip install xgboost") - except Exception: - _SkGB = None - print("[XGBoost] ne 'xgboost' ne 'scikit-learn' kurulu → XGBoost teyidi devre dışı (pip install xgboost)") - -try: # Hugging Face Spaces (ZeroGPU) — yerelde yoksa sessizce atla - import spaces - _gpu = spaces.GPU(duration=30) -except Exception: - def _gpu(fn): - return fn - -# --------------------------------------------------------------------------- -# CONFIG -# --------------------------------------------------------------------------- -OKX_BASE = "https://www.okx.com" -TIMEOUT = 10 -CACHE_TTL = 8 # saniye — aynı mum verisini sekmeler arasında tekrar çekmemek için - -POPULAR_SYMBOLS = [ - "BTC-USDT", "ETH-USDT", "SOL-USDT", "BNB-USDT", "XRP-USDT", - "DOGE-USDT", "ADA-USDT", "AVAX-USDT", "LINK-USDT", "TON-USDT", - "TRX-USDT", "DOT-USDT", "POL-USDT", "LTC-USDT", "NEAR-USDT", - "SUI-USDT", "APT-USDT", "ARB-USDT", "OP-USDT", "INJ-USDT", - "BCH-USDT", "ETC-USDT", "XLM-USDT", "ATOM-USDT", "FIL-USDT", - "HBAR-USDT", "ICP-USDT", "VET-USDT", "ALGO-USDT", "AAVE-USDT", - "UNI-USDT", "MKR-USDT", "LDO-USDT", "RUNE-USDT", "GRT-USDT", - "SAND-USDT", "MANA-USDT", "AXS-USDT", "GALA-USDT", "APE-USDT", - "CRV-USDT", "SNX-USDT", "COMP-USDT", "1INCH-USDT", "SUSHI-USDT", - "YFI-USDT", "ZEC-USDT", "DASH-USDT", "XMR-USDT", "EOS-USDT", - "XTZ-USDT", "FLOW-USDT", "THETA-USDT", "CHZ-USDT", "ENJ-USDT", - "BAT-USDT", "ZIL-USDT", "KAVA-USDT", "EGLD-USDT", "FTM-USDT", - "SEI-USDT", "TIA-USDT", "PYTH-USDT", "JUP-USDT", "WLD-USDT", - "ORDI-USDT", "STX-USDT", "IMX-USDT", "RNDR-USDT", "FET-USDT", - "AGIX-USDT", "OCEAN-USDT", "TAO-USDT", "ENA-USDT", "PENDLE-USDT", - "ONDO-USDT", "STRK-USDT", "W-USDT", "ZRO-USDT", "BLUR-USDT", - "DYDX-USDT", "GMX-USDT", "CFX-USDT", "MINA-USDT", "ROSE-USDT", - "KSM-USDT", "QTUM-USDT", "IOTA-USDT", "NEO-USDT", "ONT-USDT", - "WAVES-USDT", "CELO-USDT", "ANKR-USDT", "SKL-USDT", "STORJ-USDT", - "LRC-USDT", "BAND-USDT", "API3-USDT", "JTO-USDT", "PEPE-USDT", - "SHIB-USDT", "FLOKI-USDT", "BONK-USDT", "WIF-USDT", "TRUMP-USDT", - "NOT-USDT", "ETHFI-USDT", "BOME-USDT", "MEME-USDT", "PEOPLE-USDT", - "LUNC-USDT", "SATS-USDT", "CORE-USDT", "ZK-USDT", "IO-USDT", - "BLAST-USDT", "MANTA-USDT", "ALT-USDT", "AEVO-USDT", "OM-USDT", - "WOO-USDT", "MASK-USDT", "AR-USDT", "GAS-USDT", "LQTY-USDT", - "SSV-USDT", "MAGIC-USDT", "ID-USDT", "ARKM-USDT", "CETUS-USDT", - "GOAT-USDT", "PNUT-USDT", "MOVE-USDT", "USUAL-USDT", "VIRTUAL-USDT", -] - -BAR_OPTIONS = ["1m", "3m", "5m", "15m", "30m", "1H", "2H", "4H", "6H", "12H", "1D", "1W"] - -# approximate periods-per-year for each bar size, used to annualize risk stats -BAR_TO_PERIODS_PER_YEAR = { - "1m": 365 * 24 * 60, "3m": 365 * 24 * 20, "5m": 365 * 24 * 12, - "15m": 365 * 24 * 4, "30m": 365 * 24 * 2, "1H": 365 * 24, - "2H": 365 * 12, "4H": 365 * 6, "6H": 365 * 4, "12H": 365 * 2, - "1D": 365, "1W": 52, -} - -CHART_BG = "#0a0e14" - -# her zaman dilimi için "bir üst" zaman dilimi -> çoklu zaman dilimi (MTF) teyidi için -MTF_MAP = { - "1m": "15m", "3m": "30m", "5m": "1H", "15m": "4H", "30m": "4H", - "1H": "4H", "2H": "1D", "4H": "1D", "6H": "1D", "12H": "1W", - "1D": "1W", "1W": "1W", -} - -# --- Sinyal motoru parametreleri --- -SIGNAL_THRESHOLD = 2.0 # |net SMC teyit sayısı| >= bu değer -> AL / SAT (her teyit eşit: ±1) -STRONG_THRESHOLD = 4.0 # |net teyit| >= bu değer + konsensüs + MTF uyumu -> GÜÇLÜ -WARMUP_BARS = 60 # yapı (swing/BOS) oluşması için minimum bar -BACKTEST_LOOKBACK = 220 # walk-forward test penceresi (bar) -SL_ATR, TP_ATR = 1.5, 2.5 # backtest ve varsayılan işlem planı (ATR çarpanları) -MAX_HOLD_BARS = 24 # zaman çıkışı -DYNAMIC_EXITS = True # SL/TP'yi KNN komşularının gerçekleşmiş sapmalarından (MFE/MAE) türet; yoksa SL_ATR/TP_ATR -DYN_HORIZON = 12 # komşu sapması: sinyal barından sonraki bu kadar bar (yalnızca sonucu BİLİNEN komşular) -DYN_TP_PCT = 50 # TP1 = komşuların lehte sapmasının bu yüzdeliği (medyan); TP2 = %75, TP3 = %90 -DYN_SL_PCT = 75 # SL = komşuların aleyhte sapmasının bu yüzdeliği -DYN_TP_MIN, DYN_TP_MAX = 1.0, 4.0 # TP1 sınırları (ATR) -DYN_SL_MIN, DYN_SL_MAX = 0.8, 2.5 # SL sınırları (ATR) -DYN_MIN_NB = 8 # sapması bilinen en az komşu sayısı; altındaysa sabit SL_ATR/TP_ATR -# --- Giriş filtreleri (VARSAYILAN KAPALI; yalnızca knn_calibrate.py walk-forward OOS kanıtı bulursa knn_params.json ile açılır) --- -ENTRY_REQUIRE_ALIGN = False # True: piyasa yapısı (BOS/CHoCH trendi) sinyal yönüyle uyumlu değilse işlem yok (karşı-trend elenir) -ENTRY_MIN_ADX = 0.0 # >0: ADX bu değerin altındaysa (trend zayıf / yatay) işlem yok -ENTRY_MAX_FEE_R = 0.0 # >0: tur komisyonunun R cinsinden maliyeti (2·fee / (SL_ATR·ATR/fiyat)) bu değeri aşarsa işlem yok -# --- Pozisyon boyutlandırma: risk = kullanıcı riski × Prophet çarpanı (başka risk katmanı yok) --- -SIZE_RISK_BASE = 0.01 # backtest'te boyutlandırma ve Prophet'siz düz (flat) kıyas riski: işlem başı özkaynağın %1'i -SIZE_MAX_ALLOC = 1.0 # spot: özkaynağın en fazla %100'ü (kaldıraç yok) - -# --- PROPHET RİSK MOTORU (Meta/Facebook Prophet): TEK risk katmanı. Yalnızca risk AZALTIR (çarpan ≤ 1), yön üretmez, risk tavanını aşmaz --- -PROPHET_ENABLED = True # canlı tekil analizde çalışır (tarayıcıda kapalı: hız); PROPHET_BACKTEST açıksa backtest'e de uygulanır -PROPHET_MAX_BARS = 300 # Prophet'e verilen son mum sayısı -PROPHET_MIN_BARS = 120 # bundan az mumda Prophet çalıştırılmaz -PROPHET_HORIZON = 12 # tahmin ufku (bar) — DYN_HORIZON ile uyumlu -PROPHET_INTERVAL = 0.80 # tahmin bandı güven aralığı -PROPHET_CPS = 0.05 # changepoint_prior_scale (düşük = daha sakin trend) -PROPHET_SAMPLES = 200 # belirsizlik örneği (hız / bant kalitesi dengesi) -PROPHET_PFAV_FULL = 0.50 # P(Prophet yönü lehte) ≥ bu → yön çarpanı 1.0 -PROPHET_PFAV_VETO = 0.35 # P(lehte) < bu → VETO (V6): Prophet trendi sinyalin açıkça tersini gösteriyor -PROPHET_MULT_MIN = 0.25 # çarpan tabanı (veto yoksa) -PROPHET_FALLBACK_LITE = True # 'prophet' paketi yoksa yerleşik Prophet-lite (parçalı-doğrusal trend + Fourier mevsimsellik) kullan; False: Prophet katmanı devre dışı -PROPHET_BACKTEST = True # Prophet çarpan/vetosu backtest'e de uygulanır (walk-forward: her PROPHET_BT_STEP barda yeniden eğitilir) -PROPHET_BT_STEP = 12 # backtest'te yeniden eğitim aralığı (bar). Yalnızca o ana kadarki veriyle eğitilir; sinyal barı eğitimden sonraysa ileri tahmin kullanılır -PROPHET_BT_WORKERS = 4 # paralel Prophet eğitimi (sonuçlar önbelleğe alınır; sonraki yenilemelerde yalnızca yeni noktalar eğitilir) -BAR_FREQ = {"1m": "1min", "3m": "3min", "5m": "5min", "15m": "15min", "30m": "30min", "1H": "1h", "2H": "2h", - "4H": "4h", "6H": "6h", "12H": "12h", "1D": "1D", "1W": "7D"} -# --- Formasyon tanıma (VARSAYILAN KAPALI; knn_calibrate.py walk-forward OOS kanıtı bulursa knn_params.json ile açılır) --- -KNN_PATTERN_FEATURES = False # True: KNN özellik vektörüne mum formasyonları + son 3 mumun şekli eklenir (formasyon benzerliği) -PATTERN_VOTES = False # True: en güçlü mum formasyonu ±1 teyit oyu olarak skora eklenir (yönü ÇEVİREMEZ, KNN'e ters ise skoru düşürür) -PAT_WEIGHT = 0.7 # formasyon özelliklerinin KNN mesafesindeki göreli ağırlığı (13 temel özelliği bastırmasın) -PAT_MIN_BODY_ATR = 0.7 # engulfing: gövde en az bu kadar ATR -PAT_PIN_WICK = 2.0 # pin bar: fitil / gövde oranı alt sınırı -PAT_STAR_SMALL = 0.3 # yıldız formasyonu: ortadaki mumun gövdesi en çok bu kadar ATR -HEAT_WINDOW = 100 # hacim ısı haritası penceresi (bar) -HEAT_BINS = 40 # fiyat dilimi sayısı -HEAT_COLD = 0.30 # yoğunluk bu orandan düşükse "soğuk bölge" (hacim boşluğu) -WALL_MULT = 3.0 # emir defteri duvarı: seviye, taraf medyanının en az bu kadar katı -FEE_PER_SIDE = 0.001 # OKX spot taker ≈ %0.1 / yön -TP_R_MULTIPLES = (TP_ATR / SL_ATR, 2.5, 4.0) # TP1/TP2/TP3 (R cinsinden) - -# --- Smart Money Concepts (SMC) parametreleri --- -SMC_SWING = 3 # swing pivotu için her iki yanda gereken bar (pivot w bar sonra onaylanır) -SMC_FRESH_BARS = 8 # BOS/CHoCH bu kadar bar boyunca 'taze' sayılır -SMC_SWEEP_BARS = 5 # likidite avı bu kadar bar boyunca geçerli -SMC_OB_LOOKBACK = 40 # order block orijini için geriye bakış (bar) -SMC_OB_MAX_AGE = 120 # order block / FVG en fazla bu kadar bar yaşar -SMC_FVG_MIN_ATR = 0.15 # FVG boşluğu en az bu kadar ATR olmalı (gürültü filtresi) -SMC_FVG_KEEP = 6 # takip edilen açık FVG sayısı (yön başına) -VWAP_FRESH_BARS = 4 # VWAP kesişimi bu kadar bar boyunca 'taze' sayılır -VWAP_TOUCH_ATR = 0.25 # VWAP retest: fitil VWAP'ın bu kadar ATR yakınına değmeli -VWAP_ROLL_BARS = 48 # günlük ve üstü mumlarda oturum yok -> kayan pencere VWAP -VWAP_BAND_K = 1.0 # VWAP bantları: hacim ağırlıklı standart sapma çarpanı -KNN_K = 15 # en yakın komşu sayısı -KNN_FORWARD = 5 # komşunun etiketi: bu kadar bar sonraki getiri (yalnızca sonucu BİLİNEN barlar havuza girer) -KNN_MOVE_ATR = 0.15 # etiket eşiği: ileri getiri > +KNN_MOVE_ATR = yükseliş, < −KNN_MOVE_ATR = düşüş, arası = nötr -KNN_MIN_POOL = 60 # havuzda en az bu kadar geçmiş örnek yoksa KNN oy vermez -KNN_MIN_EDGE = 0.15 # (yükseliş − düşüş komşu payı) bu eşiği aşarsa KNN yön verir -def _knn_tiers(e): - """edge ≥ eşik sırasıyla 1 / 2 / 3 / 4 puan (varsayılan e=0.15 -> 0.15 / 0.30 / 0.50 / 0.70).""" - return (e, min(e + 0.15, 0.85), min(e + 0.35, 0.90), min(e + 0.55, 0.95)) - - -KNN_TIERS = _knn_tiers(KNN_MIN_EDGE) -KNN_POOL_MAX = 1500 # havuz: en fazla son bu kadar etiketli bar (rejim uyumu + hız) -KNN_NEUTRAL_FALLBACK = True # KNN kararsızsa (|edge| < eşik) SMC/VWAP + yapı filtresi devreye girer -SMC_PD_BAND = 0.05 # equilibrium (%50) etrafındaki nötr band -# --- XGBoost teyidi: KNN ile AYNI 13 özellik + AYNI etiket (KNN_FORWARD bar sonrası yön). Yalnızca ±1 TEYİT OYU; yönü ÇEVİREMEZ --- -XGB_ENABLED = True -XGB_STEP = 12 # yeniden eğitim aralığı (bar); eğitim yalnızca o ana kadar SONUCU BİLİNEN barlarla yapılır (walk-forward, nedensel) -XGB_TRAIN_MAX = 800 # eğitim için en fazla son bu kadar etiketli bar -XGB_MIN_TRAIN = 60 # nötr olmayan en az etiketli örnek; altında XGBoost oy vermez -XGB_N_EST, XGB_DEPTH, XGB_LR = 80, 3, 0.08 -XGB_P_VOTE = 0.58 # P(yükseliş) ≥ bu → +1 oy; ≤ 1−bu → −1 oy; arası oy yok - -# --- Kalibrasyon dosyası (knn_calibrate.py üretir; varsa varsayılanların yerine geçer) --- -KNN_PARAMS_FILE = os.path.join(os.path.dirname(os.path.abspath(__file__)), "knn_params.json") -KNN_CALIBRATION = None -if os.path.exists(KNN_PARAMS_FILE): - try: - with open(KNN_PARAMS_FILE, encoding="utf-8") as _f: - KNN_CALIBRATION = json.load(_f) - for _k in ("KNN_K", "KNN_MIN_EDGE", "KNN_FORWARD", "KNN_MOVE_ATR", "KNN_POOL_MAX", "SL_ATR", "TP_ATR", "SIGNAL_THRESHOLD", - "DYNAMIC_EXITS", "DYN_TP_PCT", "DYN_SL_PCT", - "MAX_HOLD_BARS", "ENTRY_REQUIRE_ALIGN", "ENTRY_MIN_ADX", "ENTRY_MAX_FEE_R", - "KNN_PATTERN_FEATURES", "PATTERN_VOTES"): - if _k in KNN_CALIBRATION.get("params", {}): - globals()[_k] = KNN_CALIBRATION["params"][_k] - KNN_TIERS = _knn_tiers(KNN_MIN_EDGE) - TP_R_MULTIPLES = (TP_ATR / SL_ATR, 2.5, 4.0) - print(f"[KNN] kalibrasyon yüklendi: {KNN_CALIBRATION.get('instrument')} {KNN_CALIBRATION.get('bar')} · {KNN_CALIBRATION.get('params')}") - except Exception as _e: - print(f"[KNN] kalibrasyon dosyası okunamadı, varsayılanlar kullanılıyor: {_e}") - - -# --------------------------------------------------------------------------- -# OKX PUBLIC API WRAPPERS (önbellek + yeniden deneme) -# --------------------------------------------------------------------------- -_CACHE = {} -_CACHE_LOCK = threading.Lock() - - -def _cached(key, ttl, loader): - now = time.time() - with _CACHE_LOCK: - hit = _CACHE.get(key) - if hit and now - hit[0] < ttl: - return hit[1] - val = loader() - with _CACHE_LOCK: - _CACHE[key] = (time.time(), val) - if len(_CACHE) > 200: # eski kayıtları buda - for k in sorted(_CACHE, key=lambda x: _CACHE[x][0])[:60]: - _CACHE.pop(k, None) - return val - - -def _get(path, params=None, retries=2): - for attempt in range(retries + 1): - try: - r = requests.get(f"{OKX_BASE}{path}", params=params, timeout=TIMEOUT) - r.raise_for_status() - data = r.json() - code = str(data.get("code")) - if code == "50011" and attempt < retries: # OKX rate-limit - time.sleep(0.5 * (attempt + 1)) - continue - if code != "0": - raise RuntimeError(data.get("msg") or "OKX API hata döndürdü.") - return data["data"] - except requests.exceptions.RequestException as e: - if attempt < retries: - time.sleep(0.3 * (attempt + 1)) - continue - raise RuntimeError(f"Ağ hatası: {e}") - - -def fetch_candles(inst_id, bar="1H", limit=300): - def _load(): - raw = _get("/api/v5/market/candles", {"instId": inst_id, "bar": bar, "limit": limit}) - if not raw: - raise RuntimeError(f"{inst_id} için mum verisi bulunamadı (sembolü kontrol edin).") - cols = ["ts", "o", "h", "l", "c", "vol", "volCcy", "volCcyQuote", "confirm"] - df = pd.DataFrame(raw, columns=cols) - for c in ["o", "h", "l", "c", "vol"]: - df[c] = df[c].astype(float) - df["confirm"] = df["confirm"].astype(int) - df["ts"] = pd.to_datetime(df["ts"].astype(np.int64), unit="ms") - return df.sort_values("ts").reset_index(drop=True) - - return _cached(("candles", inst_id, bar, limit), CACHE_TTL, _load).copy() - - -def fetch_ticker(inst_id): - raw = _get("/api/v5/market/ticker", {"instId": inst_id}) - return raw[0] if raw else {} - - -def fetch_all_tickers(inst_type="SPOT"): - return _cached(("tickers", inst_type), 20, lambda: _get("/api/v5/market/tickers", {"instType": inst_type})) - - -def _filter_listed(symbols): - """OKX spot'ta gerçekten listelenen sembolleri tutar (delist / yanlış çift ayıklanır). Ağ hatasında liste olduğu gibi kalır.""" - try: - live = {t["instId"] for t in fetch_all_tickers("SPOT")} - kept = [x for x in symbols if x in live] - return kept if len(kept) >= 20 else symbols - except Exception: - return symbols - - -def fetch_orderbook(inst_id, depth=20): - raw = _get("/api/v5/market/books", {"instId": inst_id, "sz": depth}) - return raw[0] if raw else {} - - -def fetch_funding_rate(inst_id): - """Spot paritenin (BTC-USDT) sürekli vadeli karşılığının (BTC-USDT-SWAP) funding oranı.""" - parts = inst_id.split("-") - if len(parts) != 2: - return None - raw = _get("/api/v5/public/funding-rate", {"instId": f"{inst_id}-SWAP"}) - if not raw: - return None - return float(raw[0].get("fundingRate") or 0.0) - - -# --------------------------------------------------------------------------- -# TECHNICAL INDICATORS -# --------------------------------------------------------------------------- -def ema(s, p): - return s.ewm(span=p, adjust=False).mean() - - -def sma(s, p): - return s.rolling(p).mean() - - -def rsi(s, p=14): - d = s.diff() - gain = d.clip(lower=0) - loss = -d.clip(upper=0) - ag = gain.ewm(alpha=1 / p, adjust=False).mean() - al = loss.ewm(alpha=1 / p, adjust=False).mean() - rs = ag / al.replace(0, np.nan) - return (100 - 100 / (1 + rs)).fillna(50) - - -def macd(s, fast=12, slow=26, signal=9): - line = ema(s, fast) - ema(s, slow) - sig = ema(line, signal) - return line, sig, line - sig - - -def vwap(df, band_k=VWAP_BAND_K): - """Nedensel VWAP + hacim ağırlıklı sapma bantları. - Gün içi mumlar: her UTC gününde sıfırlanan oturum VWAP'ı. Günlük ve üstü: kayan VWAP_ROLL_BARS penceresi. - Tipik fiyat = (H+L+C)/3. Bar i'nin değeri yalnızca <= i verisini kullanır (ileri sızıntı yok).""" - tp = (df["h"] + df["l"] + df["c"]) / 3.0 - v = df["vol"].astype(float).clip(lower=0) - step = df["ts"].diff().median() - if pd.notna(step) and step < pd.Timedelta(days=1): - g = df["ts"].dt.floor("D") - cv = v.groupby(g).cumsum() - cpv = (tp * v).groupby(g).cumsum() - cpv2 = (tp * tp * v).groupby(g).cumsum() - else: - cv = v.rolling(VWAP_ROLL_BARS, min_periods=1).sum() - cpv = (tp * v).rolling(VWAP_ROLL_BARS, min_periods=1).sum() - cpv2 = (tp * tp * v).rolling(VWAP_ROLL_BARS, min_periods=1).sum() - cv = cv.replace(0, np.nan) - vw = cpv / cv - var = (cpv2 / cv - vw * vw).clip(lower=0) - sd = np.sqrt(var) - return vw, vw + band_k * sd, vw - band_k * sd - - -def candle_patterns(df, atr_s): - """NEDENSEL mum formasyonu tanıma. Her seri bar i'de YALNIZCA ≤ i verisinden üretilir; +1 boğa, −1 ayı, 0 yok. - engulf : yutan mum (önceki mumun gövdesini tamamen yutan, ≥ PAT_MIN_BODY_ATR·ATR gövdeli ters renkli mum) - pin : çekiç / kayan yıldız (fitil ≥ PAT_PIN_WICK×gövde, fitil ≥ %60 aralık, ters fitil ≤ %25) + önceki 3 barda ters yönlü hareket - star : sabah / akşam yıldızı (3 mum: büyük gövde → küçük gövde → ters yönde ilk mumun yarısını geçen kapanış) - Dönüş: {ad: Series}""" - o, h, l, c = (df[k].to_numpy(dtype=float) for k in ("o", "h", "l", "c")) - a = atr_s.to_numpy(dtype=float) - n = len(c) - - def sh(x, k): - out = np.full(n, np.nan) - if k < n: - out[k:] = x[:n - k] - return out - - body, rng_ = np.abs(c - o), h - l - up_w, lo_w = h - np.maximum(o, c), np.minimum(o, c) - l - o1, c1, o2, c2, body1, body2 = sh(o, 1), sh(c, 1), sh(o, 2), sh(c, 2), sh(body, 1), sh(body, 2) - big = body >= PAT_MIN_BODY_ATR * a - with np.errstate(invalid="ignore"): - bull_eng = (c1 < o1) & (c > o) & (o <= c1) & (c >= o1) & big & (body > body1) - bear_eng = (c1 > o1) & (c < o) & (o >= c1) & (c <= o1) & big & (body > body1) - c4 = sh(c, 4) - down_ctx, up_ctx = c4 > c1, c4 < c1 # i-1'e kadar süren hareket (i-4 → i-1) - ok_rng = (rng_ >= 0.8 * a) & (rng_ > 0) - hammer = ok_rng & (lo_w >= PAT_PIN_WICK * body) & (lo_w >= 0.6 * rng_) & (up_w <= 0.25 * rng_) & down_ctx - shoot = ok_rng & (up_w >= PAT_PIN_WICK * body) & (up_w >= 0.6 * rng_) & (lo_w <= 0.25 * rng_) & up_ctx - small1 = body1 <= PAT_STAR_SMALL * a - morning = (c2 < o2) & (body2 >= 0.6 * a) & small1 & (c > o) & (c > (o2 + c2) / 2) - evening = (c2 > o2) & (body2 >= 0.6 * a) & small1 & (c < o) & (c < (o2 + c2) / 2) - idx = df.index - def ser(bull, bear): - return pd.Series(np.where(bull, 1.0, np.where(bear, -1.0, 0.0)), index=idx) - return {"engulf": ser(bull_eng, bear_eng), "pin": ser(hammer, shoot), "star": ser(morning, evening)} - - -PAT_LABELS = {"engulf": "Yutan mum", "pin": "Pin bar (çekiç/kayan yıldız)", "star": "Sabah/Akşam yıldızı"} - - -def pattern_features(df, ind): - """KNN için formasyon bloğu (n × 7): 3 işaretli formasyon bayrağı + son 3 mumun ATR cinsinden gövdesi + son mumun fitil eğriliği. - Sabit ölçekle bölünüp [-3, 3]'e kırpılır (tüm seri istatistiği yok → sızıntı yok) ve PAT_WEIGHT ile ağırlıklandırılır.""" - atr_ = ind["atr"].replace(0, np.nan) - o, h, l, c = df["o"], df["h"], df["l"], df["c"] - body = (c - o) / atr_ - rng_ = (h - l).replace(0, np.nan) - skew = ((np.minimum(o, c) - l) - (h - np.maximum(o, c))) / rng_ # + = alt fitil uzun (alıcı reddi) - cols = [ind["pat_engulf"], ind["pat_pin"], ind["pat_star"], body, body.shift(1), body.shift(2), skew] - P = np.column_stack([np.clip(np.nan_to_num(x.to_numpy(dtype=float), nan=0.0), -3, 3) for x in cols]) - return P * PAT_WEIGHT - - -def knn_features(df, ind, pattern=None): - """KNN özellik matrisi (n × d). Her özellik SABİT bir ölçekle bölünüp [-3, 3]'e kırpılır: - tüm seri istatistiği kullanılmadığı için ileri sızıntı yoktur. Satır i yalnızca <= i verisinden üretilir.""" - c = df["c"] - atr_ = ind["atr"].replace(0, np.nan) - sh, sl = ind["smc_sh"], ind["smc_sl"] - rng_ = (sh - sl).where(sh > sl) - cols = [ - (ind["rsi"] - 50) / 25, - (ind["stoch_k"] - 50) / 35, - (ind["adx"] - 25) / 15, - (ind["plus_di"] - ind["minus_di"]) / 20, - (c - ind["ema20"]) / atr_ / 2, - (c - ind["ema50"]) / atr_ / 4, - ind["macd_hist"] / atr_ / 0.3, - (c - ind["vwap"]) / atr_ / 2, - (c.diff(3)) / atr_ / 2, - (c.diff(10)) / atr_ / 4, - (ind["rvol"] - 1) / 1.0, - ((c - sl) / rng_ - 0.5) / 0.35, - ind["smc_trend"].astype(float), - ] - F = np.column_stack([np.clip(np.nan_to_num(x.to_numpy(dtype=float), nan=0.0), -3, 3) for x in cols]) - if KNN_PATTERN_FEATURES if pattern is None else pattern: - F = np.hstack([F, pattern_features(df, ind)]) - return F - - -def knn_labels(df, ind, forward=KNN_FORWARD, move=KNN_MOVE_ATR): - """Etiket(j) = j+forward barındaki getirinin yönü (+1 / −1 / 0). Son 'forward' bar için NaN (sonuç henüz bilinmiyor).""" - c = df["c"].to_numpy(dtype=float) - atr_ = ind["atr"].to_numpy(dtype=float) - y = np.full(len(c), np.nan) - if len(c) > forward: - fwd = (c[forward:] - c[:-forward]) / np.where(atr_[:-forward] > 0, atr_[:-forward], np.nan) - y[:-forward] = np.where(np.isnan(fwd), np.nan, np.where(fwd > move, 1.0, np.where(fwd < -move, -1.0, 0.0))) - return y - - -def knn_excursions(df, ind, horizon=DYN_HORIZON): - """Her bar için sonraki 'horizon' barda gerçekleşen azami lehte/aleyhte sapma (ATR cinsinden): - mfe = (max High − Close) / ATR (long için lehte, short için aleyhte) - mae = (Close − min Low) / ATR (short için lehte, long için aleyhte) - Son 'horizon' bar için NaN. Yalnızca SONUCU BİLİNEN komşular için kullanılır (nedensel).""" - h, l, c = (df[k].to_numpy(dtype=float) for k in ("h", "l", "c")) - atr_ = ind["atr"].to_numpy(dtype=float) - n = len(c) - hmax = pd.Series(h[::-1]).rolling(horizon, min_periods=horizon).max().to_numpy()[::-1] # max h[j .. j+H-1] - lmin = pd.Series(l[::-1]).rolling(horizon, min_periods=horizon).min().to_numpy()[::-1] - hmax, lmin = np.r_[hmax[1:], np.nan], np.r_[lmin[1:], np.nan] # max h[j+1 .. j+H] - a = np.where(atr_ > 0, atr_, np.nan) - return (hmax - c) / a, (c - lmin) / a - - -def _xgb_make(): - if XGBClassifier is not None: - return XGBClassifier(n_estimators=XGB_N_EST, max_depth=XGB_DEPTH, learning_rate=XGB_LR, subsample=0.8, colsample_bytree=0.8, - min_child_weight=3, reg_lambda=2.0, objective="binary:logistic", eval_metric="logloss", - random_state=0, n_jobs=1, verbosity=0) - if _SkGB is not None: - return _SkGB(max_iter=XGB_N_EST, max_depth=XGB_DEPTH, learning_rate=XGB_LR, l2_regularization=2.0, random_state=0) - return None - - -def _xgb_name(): - return "XGBoost" if XGBClassifier is not None else ("GradBoost (scikit-learn yedeği)" if _SkGB is not None else "yok") - - -def xgb_walkforward(F, y, n): - """Walk-forward XGBoost: her XGB_STEP barda (mutlak bar numarasına hizalı) model yeniden eğitilir. - Eğitim satırları j ≤ f − KNN_FORWARD (etiketi f anında zaten BİLİNEN barlar); tahmin f…f+STEP−1 barlarına uygulanır. - Böylece bar i için model yalnızca ≤ f ≤ i verisini görür (nedensel) ve veri kesilince aynı bar aynı değeri alır. - Etiket: yükseliş=1 / düşüş=0 (nötr etiketler eğitimden çıkarılır). Dönüş: P(yükseliş) dizisi (hesaplanamayan barlar NaN).""" - p = np.full(n, np.nan) - if not XGB_ENABLED or _xgb_make() is None: - return p - knn_start = WARMUP_BARS + KNN_MIN_POOL + KNN_FORWARD - 1 - f = (max(knn_start, n - BACKTEST_LOOKBACK - XGB_STEP) // XGB_STEP) * XGB_STEP - while f < n: - hi = f - KNN_FORWARD - lo = max(WARMUP_BARS, hi - XGB_TRAIN_MAX + 1) - if hi - lo + 1 >= XGB_MIN_TRAIN: - idx = np.arange(lo, hi + 1) - yy = y[idx] - m = np.isfinite(yy) & (yy != 0) - if m.sum() >= XGB_MIN_TRAIN and len(np.unique(yy[m])) == 2: - try: - mdl = _xgb_make() - mdl.fit(F[idx][m], (yy[m] > 0).astype(int)) - e = min(f + XGB_STEP, n) - p[f:e] = mdl.predict_proba(F[f:e])[:, 1] - except Exception: - pass - f += XGB_STEP - return p - - -def bollinger(s, p=20, k=2): - mid = sma(s, p) - std = s.rolling(p).std() - return mid + k * std, mid, mid - k * std - - -def atr(df, p=14): - h, l, c = df["h"], df["l"], df["c"] - pc = c.shift(1) - tr = pd.concat([h - l, (h - pc).abs(), (l - pc).abs()], axis=1).max(axis=1) - return tr.ewm(alpha=1 / p, adjust=False).mean() - - -def stochastic(df, k_p=14, d_p=3): - lo = df["l"].rolling(k_p).min() - hi = df["h"].rolling(k_p).max() - k = 100 * (df["c"] - lo) / (hi - lo).replace(0, np.nan) - d = k.rolling(d_p).mean() - return k.fillna(50), d.fillna(50) - - - - -def adx(df, p=14): - """ADX + yönsel indikatörler (+DI / -DI). Trend gücünü ölçer (0-100).""" - h, l, c = df["h"], df["l"], df["c"] - up_move = h.diff() - down_move = -l.diff() - plus_dm = np.where((up_move > down_move) & (up_move > 0), up_move, 0.0) - minus_dm = np.where((down_move > up_move) & (down_move > 0), down_move, 0.0) - tr = pd.concat([h - l, (h - c.shift(1)).abs(), (l - c.shift(1)).abs()], axis=1).max(axis=1) - atr_ = tr.ewm(alpha=1 / p, adjust=False).mean() - plus_di = 100 * pd.Series(plus_dm, index=df.index).ewm(alpha=1 / p, adjust=False).mean() / atr_.replace(0, np.nan) - minus_di = 100 * pd.Series(minus_dm, index=df.index).ewm(alpha=1 / p, adjust=False).mean() / atr_.replace(0, np.nan) - dx = ((plus_di - minus_di).abs() / (plus_di + minus_di).replace(0, np.nan)) * 100 - adx_ = dx.ewm(alpha=1 / p, adjust=False).mean() - return adx_.fillna(0), plus_di.fillna(0), minus_di.fillna(0) - - - - - - - - - - - - - - -def _volume_grid(h, l, vol, edges): - """Her barın hacmini, [low, high] aralığının kestiği fiyat dilimlerine eşit dağıtır. -> (bar × dilim)""" - lo_e, hi_e = edges[:-1][None, :], edges[1:][None, :] - overlap = np.clip(np.minimum(h[:, None], hi_e) - np.maximum(l[:, None], lo_e), 0.0, None) - span = np.maximum(h - l, 1e-12)[:, None] - return overlap / span * vol[:, None] - - -def volume_heat(df, window=HEAT_WINDOW, bins=HEAT_BINS): - """Hacim ısı haritası profili (nedensel: her bar yalnızca kendisine kadar olan pencereyi kullanır). - Dönüş: poc, val, vah (%70 değer alanı), heat (kapanışın bulunduğu dilimin yoğunluğu / en yoğun dilim, 0..1)""" - h, l, c, v = (df[k].to_numpy(dtype=float) for k in ("h", "l", "c", "vol")) - n = len(c) - poc, val, vah, heat = (np.full(n, np.nan) for _ in range(4)) - for i in range(min(window, n) - 1, n): - a = max(0, i - window + 1) - hh, ll = h[a:i + 1], l[a:i + 1] - top, bot = hh.max(), ll.min() - if top <= bot: - continue - edges = np.linspace(bot, top, bins + 1) - prof = _volume_grid(hh, ll, v[a:i + 1], edges).sum(axis=0) - if prof.max() <= 0: - continue - mids = (edges[:-1] + edges[1:]) / 2 - k = int(prof.argmax()) - poc[i] = mids[k] - # değer alanı: POC'tan başlayıp toplam hacmin %70'ine ulaşana dek komşu dilimleri ekle - lo_k = hi_k = k - acc, tot = prof[k], prof.sum() - while acc < 0.7 * tot and (lo_k > 0 or hi_k < bins - 1): - up = prof[hi_k + 1] if hi_k < bins - 1 else -1.0 - dn = prof[lo_k - 1] if lo_k > 0 else -1.0 - if up >= dn: - hi_k += 1; acc += prof[hi_k] - else: - lo_k -= 1; acc += prof[lo_k] - val[i], vah[i] = edges[lo_k], edges[hi_k + 1] - ci = int(np.clip(np.searchsorted(edges, c[i], side="right") - 1, 0, bins - 1)) - heat[i] = prof[ci] / prof.max() - return (pd.Series(x, index=df.index) for x in (poc, val, vah, heat)) - - -def book_walls(ob, price, atr_v, mult=WALL_MULT, reach=1.5): - """Emir defterindeki 'sıcak' likidite duvarlarını bulur (yakın ve taraf medyanına göre çok büyük seviyeler). - Dönüş: (destek_duvarı, direnç_duvarı) — her biri (fiyat, büyüklük_kat) ya da None.""" - def pick(levels, below): - if not levels or atr_v <= 0: - return None - sizes = np.array([float(x[1]) for x in levels]) - px = np.array([float(x[0]) for x in levels]) - med = float(np.median(sizes)) - if med <= 0: - return None - dist = (price - px) / atr_v if below else (px - price) / atr_v - ok = (sizes >= mult * med) & (dist > 0) & (dist <= reach) - if not ok.any(): - return None - j = int(np.where(ok, sizes, -1).argmax()) - return float(px[j]), float(sizes[j] / med) - return pick(ob.get("bids"), True), pick(ob.get("asks"), False) - - - - - - - - - - - - - - -def find_swings(series, window=3): - """Yerel tepe/dip (swing high/low) indekslerini bulur.""" - n = len(series) - highs, lows = [], [] - for i in range(window, n - window): - seg = series.iloc[i - window:i + window + 1] - if series.iloc[i] == seg.max(): - highs.append(i) - if series.iloc[i] == seg.min(): - lows.append(i) - return highs, lows - - - - - - -def find_sr_levels(df, atr_v, window=5, lookback=200, max_levels=4): - """Swing tepe/diplerini kümeleyerek destek/direnç seviyeleri çıkarır. - Dönüş: (destekler, dirençler) — her biri [(seviye, temas_sayısı), ...], fiyata yakınlık sırasıyla.""" - sub = df.iloc[-lookback:].reset_index(drop=True) - highs, _ = find_swings(sub["h"], window) - _, lows = find_swings(sub["l"], window) - pts = [float(sub["h"].iloc[i]) for i in highs] + [float(sub["l"].iloc[i]) for i in lows] - if not pts: - return [], [] - price = float(df["c"].iloc[-1]) - tol = max(0.6 * (atr_v if atr_v and atr_v > 0 else 0.0), price * 0.002) - pts.sort() - clusters, cur = [], [pts[0]] - for p in pts[1:]: - if p - float(np.mean(cur)) <= tol: - cur.append(p) - else: - clusters.append(cur) - cur = [p] - clusters.append(cur) - levels = [(float(np.mean(c)), len(c)) for c in clusters] - supports = sorted([lv for lv in levels if lv[0] < price], key=lambda x: -x[0])[:max_levels] - resists = sorted([lv for lv in levels if lv[0] > price], key=lambda x: x[0])[:max_levels] - return supports, resists - - -def smc_structure(df, atr_s, w=SMC_SWING): - """Smart Money Concepts yapısı — tamamen NEDENSEL (bar i yalnızca i'ye kadar olan veriyi kullanır; - swing pivotları `w` bar sonra onaylanır). Üretir: - * Piyasa yapısı: son onaylı swing high/low'un KAPANIŞLA kırılması. Trend yönünde kırılım = BOS (devam), - trende karşı kırılım = CHoCH (dönüş). - * Order Block: kırılımı başlatan hareketin orijinindeki son karşı yönlü mum (kapanışla aşılırsa geçersiz). - * Fair Value Gap: 3 mumluk fiyat boşluğu (kapanışla tamamen dolarsa geçersiz) + retest takibi. - * Likidite avı (sweep): fitil swing seviyesini aşar, kapanış seviyenin gerisinde kalır. - Dönüş: (seri sözlüğü, grafik için bölgeler)""" - o, h, l, c = (df[k].to_numpy(dtype=float) for k in ("o", "h", "l", "c")) - a = atr_s.to_numpy(dtype=float) - n = len(c) - full = lambda v: np.full(n, v, dtype=float) - out = {k: full(np.nan) for k in ("sh", "sl", "obb_lo", "obb_hi", "obs_lo", "obs_hi", "swb_px", "sws_px")} - out.update({k: full(0.0) for k in ("trend", "brk_dir", "brk_choch", "fvgb", "fvgs")}) - out.update({k: full(-1.0) for k in ("brk_i", "swb_i", "sws_i")}) - - last_sh = last_sl = None # (indeks, fiyat) — son onaylı swing high / low - sh_broken = sl_broken = True - sh_swept = sl_swept = False - trend = 0 - brk_dir, brk_i, brk_choch = 0, -1, 0 - bull_ob = bear_ob = None # (alt, üst, oluşum_bar, mum_bar) - bull_fvg, bear_fvg = [], [] # [(alt, üst, oluşum_bar), ...] - swb_i = sws_i = -1 - swb_px = sws_px = np.nan - events, sweeps = [], [] - - for i in range(n): - j = i - w - if j >= w: - if h[j] == h[j - w:j + w + 1].max(): - last_sh, sh_broken, sh_swept = (j, h[j]), False, False - if l[j] == l[j - w:j + w + 1].min(): - last_sl, sl_broken, sl_swept = (j, l[j]), False, False - ci = c[i] - - # --- yapı kırılımı (kapanışla): BOS / CHoCH + order block --- - if last_sh and not sh_broken and ci > last_sh[1]: - choch = 1 if trend < 0 else 0 - events.append({"i": i, "pivot_i": last_sh[0], "dir": 1, "choch": bool(choch), "level": float(last_sh[1])}) - trend, sh_broken = 1, True - brk_dir, brk_i, brk_choch = 1, i, choch - a0 = max(last_sl[0] if last_sl else 0, i - SMC_OB_LOOKBACK) - cand = [k for k in range(a0, i) if c[k] < o[k]] # düşüş mumları - if cand: - k = min(cand, key=lambda x: l[x]) # hareketin orijini: en düşük düşüş mumu - bull_ob = (float(l[k]), float(h[k]), i, k) - elif last_sl and not sl_broken and ci < last_sl[1]: - choch = 1 if trend > 0 else 0 - events.append({"i": i, "pivot_i": last_sl[0], "dir": -1, "choch": bool(choch), "level": float(last_sl[1])}) - trend, sl_broken = -1, True - brk_dir, brk_i, brk_choch = -1, i, choch - a0 = max(last_sh[0] if last_sh else 0, i - SMC_OB_LOOKBACK) - cand = [k for k in range(a0, i) if c[k] > o[k]] # yükseliş mumları - if cand: - k = max(cand, key=lambda x: h[x]) - bear_ob = (float(l[k]), float(h[k]), i, k) - - # --- likidite avı (sweep): fitil seviyeyi aşar, kapanış geri döner --- - if last_sh and not sh_broken and not sh_swept and h[i] > last_sh[1] and ci <= last_sh[1]: - sh_swept, sws_i, sws_px = True, i, float(h[i]) - sweeps.append({"i": i, "dir": -1, "level": float(last_sh[1])}) # buy-side likidite alındı - if last_sl and not sl_broken and not sl_swept and l[i] < last_sl[1] and ci >= last_sl[1]: - sl_swept, swb_i, swb_px = True, i, float(l[i]) - sweeps.append({"i": i, "dir": 1, "level": float(last_sl[1])}) # sell-side likidite alındı - - # --- order block mitigasyonu (kapanışla aşıldıysa ya da çok eskiyse geçersiz) --- - if bull_ob and bull_ob[2] < i and (ci < bull_ob[0] or i - bull_ob[2] > SMC_OB_MAX_AGE): - bull_ob = None - if bear_ob and bear_ob[2] < i and (ci > bear_ob[1] or i - bear_ob[2] > SMC_OB_MAX_AGE): - bear_ob = None - - # --- Fair Value Gap: oluşum, mitigasyon, retest --- - if i >= 2: - min_gap = SMC_FVG_MIN_ATR * a[i] if a[i] == a[i] else 0.0 - if l[i] > h[i - 2] and l[i] - h[i - 2] >= min_gap: - bull_fvg.append((float(h[i - 2]), float(l[i]), i)) - if h[i] < l[i - 2] and l[i - 2] - h[i] >= min_gap: - bear_fvg.append((float(h[i]), float(l[i - 2]), i)) - bull_fvg = [z for z in bull_fvg if not (z[2] < i and ci < z[0]) and i - z[2] <= SMC_OB_MAX_AGE][-SMC_FVG_KEEP:] - bear_fvg = [z for z in bear_fvg if not (z[2] < i and ci > z[1]) and i - z[2] <= SMC_OB_MAX_AGE][-SMC_FVG_KEEP:] - out["fvgb"][i] = 1.0 if any(z[2] <= i - 2 and l[i] <= z[1] and ci >= z[0] for z in bull_fvg) else 0.0 - out["fvgs"][i] = 1.0 if any(z[2] <= i - 2 and h[i] >= z[0] and ci <= z[1] for z in bear_fvg) else 0.0 - - out["trend"][i] = trend - out["brk_dir"][i], out["brk_i"][i], out["brk_choch"][i] = brk_dir, brk_i, brk_choch - if last_sh: - out["sh"][i] = last_sh[1] - if last_sl: - out["sl"][i] = last_sl[1] - if bull_ob: - out["obb_lo"][i], out["obb_hi"][i] = bull_ob[0], bull_ob[1] - if bear_ob: - out["obs_lo"][i], out["obs_hi"][i] = bear_ob[0], bear_ob[1] - out["swb_i"][i], out["sws_i"][i], out["swb_px"][i], out["sws_px"][i] = swb_i, sws_i, swb_px, sws_px - - zones = {"bull_ob": bull_ob, "bear_ob": bear_ob, "bull_fvg": bull_fvg, "bear_fvg": bear_fvg, - "events": events, "sweeps": sweeps} - return {k: pd.Series(v, index=df.index) for k, v in out.items()}, zones - - -def compute_indicators(df, xgb=False): - close = df["c"] - ind = {} - # --- bilgi amaçlı göstergeler (SİNYALE DAHİL DEĞİL) --- - ind["rsi"] = rsi(close) - ind["macd_line"], ind["macd_signal"], ind["macd_hist"] = macd(close) - ind["ema20"] = ema(close, 20) - ind["ema50"] = ema(close, 50) - ind["ema200"] = ema(close, 200 if len(close) >= 200 else max(5, len(close) // 2)) - ind["bb_upper"], ind["bb_mid"], ind["bb_lower"] = bollinger(close) - ind["atr"] = atr(df) - ind["vwap"], ind["vwap_up"], ind["vwap_dn"] = vwap(df) - ind["stoch_k"], ind["stoch_d"] = stochastic(df) - ind["adx"], ind["plus_di"], ind["minus_di"] = adx(df) - ind["vp_poc"], ind["vp_val"], ind["vp_vah"], ind["vp_heat"] = volume_heat(df) - ind["rvol"] = (df["vol"] / sma(df["vol"], 20).replace(0, np.nan)).fillna(1.0) - ind["atr_rank"] = (ind["atr"] / close).rolling(100, min_periods=30).apply( - lambda x: float((x <= x[-1]).mean()), raw=True).fillna(0.5) - for _k, _v in candle_patterns(df, ind["atr"]).items(): - ind["pat_" + _k] = _v - # --- Smart Money Concepts (sinyalin tek kaynağı) --- - smc, zones = smc_structure(df, ind["atr"]) - for k, v in smc.items(): - ind["smc_" + k] = v - # hızlı, nedensel erişim için numpy önbelleği (backtest döngüsünde .iloc yerine kullanılır) - arr = {k: v.to_numpy(dtype=float) for k, v in ind.items()} - for k in ("o", "h", "l", "c", "vol"): - arr[k] = df[k].to_numpy(dtype=float) - arr["knn_F"] = knn_features(df, ind) - arr["knn_y"] = knn_labels(df, ind) - arr["knn_mfe"], arr["knn_mae"] = knn_excursions(df, ind) - arr["xgb_p"] = xgb_walkforward(arr["knn_F"], arr["knn_y"], len(df)) if xgb else np.full(len(df), np.nan) - ind["_arr"] = arr - ind["smc_zones"] = zones - return ind - - -# --------------------------------------------------------------------------- -# COMPOSITE SIGNAL ENGINE v2 (multi-factor, rejim-uyarlamalı, konsensüs filtreli) -# --------------------------------------------------------------------------- -def _dyn_levels(A, nn, i, d): - """KNN komşularının GERÇEKLEŞMİŞ sapmalarından dinamik SL/TP (ATR çarpanı). Yalnızca j + DYN_HORIZON <= i olan komşular.""" - ok = nn[nn <= i - DYN_HORIZON] - mfe, mae = A["knn_mfe"][ok], A["knn_mae"][ok] - fav, adv = (mfe, mae) if d > 0 else (mae, mfe) - fav, adv = fav[~np.isnan(fav)], adv[~np.isnan(adv)] - if len(fav) < DYN_MIN_NB or len(adv) < DYN_MIN_NB: - return None - sl = float(np.clip(np.percentile(adv, DYN_SL_PCT), DYN_SL_MIN, DYN_SL_MAX)) - tp1 = float(np.clip(np.percentile(fav, DYN_TP_PCT), DYN_TP_MIN, DYN_TP_MAX)) - tp1 = max(tp1, sl) # en az 1R - tp2 = float(np.clip(np.percentile(fav, 75), tp1 * 1.25, DYN_TP_MAX * 1.5)) - tp3 = float(np.clip(np.percentile(fav, 90), tp2 * 1.25, DYN_TP_MAX * 2.0)) - return {"dir": int(d), "sl": sl, "tp": tp1, "tp2": tp2, "tp3": tp3, "n": int(len(fav))} - - -def apply_entry_filters(res, A, i): - """Giriş filtreleri. Skor YÖNÜNÜ çevirmez, yalnızca zayıf/pahalı girişleri eler (score=0). Tüm filtreler nedensel (≤ i verisi). - Canlı sinyal, tarayıcı ve backtest bu fonksiyonu _score_at üzerinden AYNI şekilde kullanır; kalibratör önbellekli - filtresiz skorlara bunu sonradan uygular. Dönüş: değişmediyse aynı nesne, elendiyse score=0 olan kopya.""" - s = res["score"] - if s == 0 or not (ENTRY_REQUIRE_ALIGN or ENTRY_MIN_ADX > 0 or ENTRY_MAX_FEE_R > 0): - return res - d = 1 if s > 0 else -1 - why = None - if ENTRY_REQUIRE_ALIGN and res["trend"] * d <= 0: - why = "Filtre: piyasa yapısı sinyal yönüyle uyumlu değil — karşı-trend işlem elendi" - elif ENTRY_MIN_ADX > 0: - adx_i = A["adx"][i] - if not (adx_i >= ENTRY_MIN_ADX): - why = f"Filtre: ADX {adx_i:.0f} < {ENTRY_MIN_ADX:g} — trend zayıf, işlem elendi" - if why is None and ENTRY_MAX_FEE_R > 0: - lv = res.get("levels") - sl_m = lv["sl"] if (lv and lv["dir"] == d) else SL_ATR - a_i, px = A["atr"][i], A["c"][i] - if a_i > 0 and px > 0: - fee_r = 2 * FEE_PER_SIDE / (sl_m * a_i / px) - if fee_r > ENTRY_MAX_FEE_R: - why = f"Filtre: komisyon maliyeti {fee_r:.2f}R > {ENTRY_MAX_FEE_R:g}R — volatilite komisyonu karşılamıyor" - if why is None: - return res - out = dict(res) - out["score"], out["levels"], out["gate"] = 0.0, None, why - if "reasons" in out: - out["reasons"] = list(out["reasons"]) + [("flat", why)] - return out - - -def _score_at(ind, i, verbose=False): - """Smart Money Concepts teyit sayımı — AĞIRLIK YOK. Verilen bar indeksinde yalnızca o ana kadarki veri kullanılır. - - [YEDEK MOD] YÖN FİLTRESİ (yalnızca KNN kullanılamazsa): piyasa yapısı. Son kırılım yukarıysa (BOS/CHoCH ▲) yalnızca LONG, - aşağıysa yalnızca SHORT aranır. Yapıya ters teyitler varsa sinyal verilmez — önce CHoCH beklenir. - - TEYİTLER (her biri eşit: +1 boğa / −1 ayı / 0 yok): - 1) Taze BOS / CHoCH (son SMC_FRESH_BARS bar içinde yapı kırılımı) - 2) Premium / Discount (swing aralığının alt %45'i = discount → +1, üst %45'i = premium → −1) - 3) Order Block (fiyat aktif talep / arz bloğuna değdi) - 4) Fair Value Gap retest (fiyat açık FVG'ye geri döndü) - 5) Likidite avı (sweep) (son SMC_SWEEP_BARS bar içinde swing fitille süpürülüp geri dönüldü) - 6) Acheron VWAP etkileşimi (VWAP'ı taze yukarı/aşağı kesme veya VWAP'a retest + kapanışla tutunma) - - ANA MOTOR: KNN (bkz. modül başlığı) yönü ve temel puanı (2–4) verir; yukarıdaki 6 teyit net olarak eklenir/çıkarılır. - Skor = KNN puanı·yön + net teyit (−10…+10). Canlı sinyal ve backtest AYNI fonksiyonu kullanır.""" - A = ind["_arr"] - price, hi_i, lo_i = A["c"][i], A["h"][i], A["l"][i] - trend = int(A["smc_trend"][i]) - votes, tags = [], [] - long_refs, short_refs = [], [] # stop yerleşimi için SMC referans seviyeleri - - def vote(label, d, text, tag=None): - votes.append((label, int(d), text)) - if tag: - tags.append((tag, int(d))) - - # --- 1) taze BOS / CHoCH --- - bi = A["smc_brk_i"][i] - if bi >= 0 and i - bi <= SMC_FRESH_BARS: - d = int(A["smc_brk_dir"][i]) - name = "CHoCH" if A["smc_brk_choch"][i] > 0 else "BOS" - arrow = "▲" if d > 0 else "▼" - age = int(i - bi) - when = "bu barda" if age == 0 else f"{age} bar önce" - kind = "trend dönüşü" if name == "CHoCH" else "trend devamı" - vote(name, d, f"{name} {arrow}: piyasa yapısı {when} {'yukarı' if d > 0 else 'aşağı'} kırıldı ({kind})", - f"{name} {arrow}") - - # --- 2) Premium / Discount --- - sh, sl = A["smc_sh"][i], A["smc_sl"][i] - pd_pos = None - if sh == sh and sl == sl and sh > sl: - pd_pos = float((price - sl) / (sh - sl)) - if pd_pos <= 0.5 - SMC_PD_BAND: - vote("Discount", 1, f"Fiyat swing aralığının DISCOUNT bölgesinde (%{pd_pos * 100:.0f}) — alım için ucuz bölge", "DISCOUNT") - elif pd_pos >= 0.5 + SMC_PD_BAND: - vote("Premium", -1, f"Fiyat swing aralığının PREMIUM bölgesinde (%{pd_pos * 100:.0f}) — satım için pahalı bölge", "PREMIUM") - else: - vote("Denge", 0, f"Fiyat equilibrium (%50) civarında (%{pd_pos * 100:.0f}) — bölge avantajı yok") - - # --- 3) Order Block --- - bl, bh = A["smc_obb_lo"][i], A["smc_obb_hi"][i] - if bl == bl and lo_i <= bh and price >= bl: - vote("Order Block ▲", 1, f"Fiyat aktif talep (bullish) ORDER BLOCK bölgesinde ({fmt_price(bl)} – {fmt_price(bh)})", "OB ▲") - long_refs.append(float(bl)) - rl, rh = A["smc_obs_lo"][i], A["smc_obs_hi"][i] - if rl == rl and hi_i >= rl and price <= rh: - vote("Order Block ▼", -1, f"Fiyat aktif arz (bearish) ORDER BLOCK bölgesinde ({fmt_price(rl)} – {fmt_price(rh)})", "OB ▼") - short_refs.append(float(rh)) - - # --- 4) Fair Value Gap retest --- - if A["smc_fvgb"][i] > 0: - vote("FVG ▲", 1, "Fiyat açık yükseliş FVG'sine (dengesizlik boşluğu) geri döndü — destek bölgesi", "FVG ▲") - if A["smc_fvgs"][i] > 0: - vote("FVG ▼", -1, "Fiyat açık düşüş FVG'sine (dengesizlik boşluğu) geri döndü — direnç bölgesi", "FVG ▼") - - # --- 5) Likidite avı --- - sb, ss = A["smc_swb_i"][i], A["smc_sws_i"][i] - if sb >= 0 and i - sb <= SMC_SWEEP_BARS: - vote("Likidite Avı ▲", 1, f"Sell-side likidite alındı ({int(i - sb)} bar önce): dip fitille süpürüldü, fiyat geri döndü", "SWEEP ▲") - long_refs.append(float(A["smc_swb_px"][i])) - if ss >= 0 and i - ss <= SMC_SWEEP_BARS: - vote("Likidite Avı ▼", -1, f"Buy-side likidite alındı ({int(i - ss)} bar önce): tepe fitille süpürüldü, fiyat geri döndü", "SWEEP ▼") - short_refs.append(float(A["smc_sws_px"][i])) - - # --- 6) Acheron VWAP etkileşimi --- - vw = A["vwap"][i] - if vw == vw: - atr_i = A["atr"][i] if A["atr"][i] == A["atr"][i] else 0.0 - cl = A["c"] - vwv = A["vwap"] - cross_d, cross_age = 0, None - for k in range(0, VWAP_FRESH_BARS + 1): # en yakın taze kesişimi bul (yalnızca geçmiş barlar) - j = i - k - if j < 1 or vwv[j] != vwv[j] or vwv[j - 1] != vwv[j - 1]: - break - if cl[j - 1] <= vwv[j - 1] and cl[j] > vwv[j]: - cross_d, cross_age = 1, k - break - if cl[j - 1] >= vwv[j - 1] and cl[j] < vwv[j]: - cross_d, cross_age = -1, k - break - if cross_d and ((cross_d > 0 and price > vw) or (cross_d < 0 and price < vw)): - when = "bu barda" if cross_age == 0 else f"{cross_age} bar önce" - vote("Acheron VWAP " + ("▲" if cross_d > 0 else "▼"), cross_d, - f"Fiyat VWAP'ı ({fmt_price(vw)}) {when} {'yukarı' if cross_d > 0 else 'aşağı'} kesti ve tutunuyor", - "VWAP ▲" if cross_d > 0 else "VWAP ▼") - elif price > vw and lo_i <= vw + VWAP_TOUCH_ATR * atr_i: - vote("Acheron VWAP ▲", 1, f"Fiyat VWAP'a ({fmt_price(vw)}) geri test yaptı ve üstünde kapandı — dinamik destek", "VWAP ▲") - elif price < vw and hi_i >= vw - VWAP_TOUCH_ATR * atr_i: - vote("Acheron VWAP ▼", -1, f"Fiyat VWAP'a ({fmt_price(vw)}) geri test yaptı ve altında kapandı — dinamik direnç", "VWAP ▼") - - # --- 7) Mum formasyonu (isteğe bağlı teyit; en güçlü TEK formasyon ±1 oy) --- - if PATTERN_VOTES: - for pk in ("engulf", "star", "pin"): # öncelik: yutan > yıldız > pin - pv = int(A["pat_" + pk][i]) - if pv: - vote(PAT_LABELS[pk] + (" ▲" if pv > 0 else " ▼"), pv, - f"{PAT_LABELS[pk]} {'boğa' if pv > 0 else 'ayı'} formasyonu bu barda oluştu", pk.upper() + (" ▲" if pv > 0 else " ▼")) - break - - # --- 8) XGBoost (walk-forward eğitilmiş; KNN ile aynı özellik/etiket) — ±1 teyit oyu, yönü çeviremez --- - xp = float(A["xgb_p"][i]) if XGB_ENABLED else float("nan") - if xp == xp: - if xp >= XGB_P_VOTE: - vote("XGBoost ▲", 1, f"XGBoost: {KNN_FORWARD} bar sonrası yükseliş olasılığı %{xp * 100:.0f}", "XGB ▲") - elif xp <= 1.0 - XGB_P_VOTE: - vote("XGBoost ▼", -1, f"XGBoost: {KNN_FORWARD} bar sonrası düşüş olasılığı %{(1 - xp) * 100:.0f}", "XGB ▼") - - # --- ANA MOTOR: KNN — şimdiki duruma en benzer geçmiş barlar --- - knn = None - F, Y = A["knn_F"], A["knn_y"] - hi_pool = i - KNN_FORWARD # j + forward <= i -> etiket i anında zaten biliniyor - lo_pool = max(WARMUP_BARS, hi_pool - KNN_POOL_MAX + 1) - if hi_pool - lo_pool + 1 >= KNN_MIN_POOL: - pool = np.arange(lo_pool, hi_pool + 1) - dist = np.sqrt(((F[pool] - F[i]) ** 2).sum(axis=1)) - order = np.argsort(dist, kind="stable") - nn = pool[order[:KNN_K]] - ys = Y[nn] - ys = ys[~np.isnan(ys)] - if len(ys) >= max(5, KNN_K // 2): - nb, ns = int((ys > 0).sum()), int((ys < 0).sum()) - edge = (nb - ns) / len(ys) - pts = sum(abs(edge) >= t for t in KNN_TIERS) # 0 = kararsız, 1…4 = yön gücü - knn = {"edge": edge, "n": len(ys), "up": nb, "down": ns, "pts": int(pts), "nn": nn, - "dir": (1 if edge > 0 else -1) if pts else 0} - - # --- skor birleştirme --- - net = sum(d for _, d, _ in votes) - bull_w = float(sum(1 for _, d, _ in votes if d > 0)) - bear_w = float(sum(1 for _, d, _ in votes if d < 0)) - gate = None - knn_reason = None - - def smc_only(tag): - """Yalnızca SMC/VWAP net teyit sayısı + piyasa yapısı filtresi (eski kural).""" - if trend == 0: - return 0.0, f"{tag} piyasa yapısı belirsiz — sinyal üretilmez" - if (net > 0 and trend < 0) or (net < 0 and trend > 0): - return 0.0, (f"{tag} teyitler {'yükseliş' if net > 0 else 'düşüş'} yönlü ama piyasa yapısı " - f"{'düşüş' if trend < 0 else 'yükseliş'} — yapı dönmeden (CHoCH) işlem yok") - return float(net), f"{tag} yalnızca SMC/VWAP teyitleri kullanıldı" - - if knn is not None: - kd, kp = knn["dir"], knn["pts"] - knn_txt = (f"KNN: en benzer {knn['n']} geçmiş durumdan {knn['up']} tanesi {KNN_FORWARD} bar sonra yükseldi, " - f"{knn['down']} tanesi düştü (net %{knn['edge'] * 100:+.0f})") - if kd == 0: - if KNN_NEUTRAL_FALLBACK: - knn_reason = (0, knn_txt + " — yön vermiyor, SMC/VWAP'a devredildi") - score, gate = smc_only("[KNN kararsız]") - else: - score = 0.0 - knn_reason = (0, knn_txt + " — yön vermiyor") - gate = "KNN ana motoru yön vermiyor (komşular kararsız) — işlem yok" - else: - tags.insert(0, ("KNN ▲" if kd > 0 else "KNN ▼", kd)) - knn_reason = (kd, f"{knn_txt} → {kp} puan") - if kd > 0: - bull_w += kp - else: - bear_w += kp - total = kd * kp + net - if total * kd <= 0: - score = 0.0 - gate = f"SMC/VWAP teyitleri KNN yönüne ters ({net:+d}) — KNN yönü çevrilemez, işlem yok" - else: - score = float(total) - if (trend > 0 and kd < 0) or (trend < 0 and kd > 0): - gate = "Dikkat: piyasa yapısı KNN yönüne ters — işlem karşı-trend" - else: - # YEDEK MOD: KNN için yeterli geçmiş yok - score, gate = smc_only("[Yedek mod: KNN için yeterli geçmiş yok]") - - levels = None - if DYNAMIC_EXITS and knn is not None and knn["dir"] != 0 and score != 0 and (score > 0) == (knn["dir"] > 0): - levels = _dyn_levels(A, knn["nn"], i, knn["dir"]) - - regime = "YÜKSELİŞ" if trend > 0 else ("DÜŞÜŞ" if trend < 0 else "BELİRSİZ") - if knn is not None: - knn = {k: v for k, v in knn.items() if k != "nn"} - out = { - "score": score, "xgb_p": (xp if xp == xp else None), "price": price, "regime": regime, "trend": trend, "net": net, "knn": knn, "levels": levels, - "bull_w": bull_w, "bear_w": bear_w, "atr_rank": A["atr_rank"][i], "pd": pd_pos, "tags": tags, - "gate": gate, "heat": A["vp_heat"][i], - "stop_ref": {"long": min(long_refs) if long_refs else None, "short": max(short_refs) if short_refs else None}, - } - if verbose: - def tone(d): - return "up" if d > 0 else ("down" if d < 0 else "flat") - bars_txt = "" - if bi >= 0: - bars_txt = f" — son kırılım {int(i - bi)} bar önce ({'CHoCH' if A['smc_brk_choch'][i] > 0 else 'BOS'} {'▲' if A['smc_brk_dir'][i] > 0 else '▼'})" - struct_txt = {1: "Piyasa yapısı YÜKSELİŞ (HH/HL) — yalnızca long aranır", - -1: "Piyasa yapısı DÜŞÜŞ (LH/LL) — yalnızca short aranır", - 0: "Piyasa yapısı belirsiz"}[trend] + (bars_txt if trend else "") - reasons = [] - if knn_reason: - reasons.append((tone(knn_reason[0]), knn_reason[1])) - reasons.append((tone(trend), struct_txt)) - reasons += [(tone(d), text) for _, d, text in votes] - if levels: - reasons.append(("flat", f"Dinamik çıkış (KNN komşuları, {levels['n']} örnek): SL {levels['sl']:.2f}×ATR · " - f"TP {levels['tp']:.2f} / {levels['tp2']:.2f} / {levels['tp3']:.2f}×ATR")) - if gate: - reasons.append(("flat", gate)) - out["reasons"] = reasons - out["contribs"] = [(lab, float(d)) for lab, d, _ in votes if d != 0] - if knn and knn["dir"]: - out["contribs"].insert(0, ("KNN " + ("▲" if knn["dir"] > 0 else "▼"), float(knn["dir"] * knn["pts"]))) - return apply_entry_filters(out, A, i) - - -BACKTEST_MIN_BARS = 40 # KNN havuzu dolduktan sonra backtest penceresinde kalması gereken en az bar - - -def compute_score_series(ind, n): - """Son BACKTEST_LOOKBACK bar için (canlı bar dahil) skor serisi. Dönüş: (başlangıç_indeksi, [skor_dict,...]) - Pencere, KNN havuzunun dolduğu ilk bardan (WARMUP_BARS + KNN_MIN_POOL + KNN_FORWARD − 1) önce BAŞLAMAZ: aksi halde ilk barlar - canlıda kullanılan KNN ana motoru yerine SMC yedek modunda koşar ve backtest iki farklı stratejinin karışımı olur. - Veri bunun için çok kısaysa (canlı sinyal de yedek moddadır) pencere eskisi gibi kalır.""" - start = min(max(WARMUP_BARS, n - BACKTEST_LOOKBACK), n - 1) - knn_start = WARMUP_BARS + KNN_MIN_POOL + KNN_FORWARD - 1 - if n - knn_start >= BACKTEST_MIN_BARS: - start = max(start, knn_start) - return start, [_score_at(ind, i) for i in range(start, n)] - - -# --------------------------------------------------------------------------- -# BACKTEST — yön doğruluğu + gerçek işlem simülasyonu (SL/TP/zaman çıkışı + komisyon) -# --------------------------------------------------------------------------- -def directional_accuracy(df, start, scores, forward=5, threshold=SIGNAL_THRESHOLD): - """Sinyal 'forward' bar sonra doğru yönü göstermiş mi? ÜST ÜSTE BİNMEYEN sinyaller sayılır (bir sinyalden sonra - 'forward' bar atlanır) — aksi halde ardışık barların bağımlı sinyalleri doğruluğu ve örnek sayısını şişirir. - Ek olarak yön-ayarlı ortalama getiri (mean_ret) ve komisyon sonrası değeri (net_ret) verilir: doğruluk %58 olsa da - doğru hareketler yanlışlardan küçükse veya tur komisyonunu (2·FEE_PER_SIDE) aşmıyorsa işlem zarar eder.""" - c = df["c"].to_numpy() - n = len(c) - stats = {"all": [0, 0, 0.0], "long": [0, 0, 0.0], "short": [0, 0, 0.0]} - i = start - while i < n - forward: - s = scores[i - start] - if abs(s["score"]) < threshold: - i += 1 - continue - up = s["score"] > 0 - ret = (1 if up else -1) * (c[i + forward] / c[i] - 1) - for key in ("all", "long" if up else "short"): - stats[key][1] += 1 - stats[key][2] += ret - if ret > 0: - stats[key][0] += 1 - i += forward - if stats["all"][1] < 5: - return None - - def pack(key): - ok, t, tot = stats[key] - return {"accuracy": ok / t, "n_signals": t, "mean_ret": tot / t} if t > 0 else None - - mr = stats["all"][2] / stats["all"][1] - return { - "accuracy": stats["all"][0] / stats["all"][1], "correct": stats["all"][0], - "n_signals": stats["all"][1], "forward_bars": forward, - "mean_ret": mr, "net_ret": mr - 2 * FEE_PER_SIDE, - "long_side": pack("long"), "short_side": pack("short"), - } - - -def simulate_trades(df, ind, start, scores, threshold=SIGNAL_THRESHOLD): - """Sinyalleri sırayla (üst üste binmeden) işleme çevirir. - Giriş: sinyal barını izleyen barın AÇILIŞI (gerçekçi, ileri sızıntı yok). - Çıkış: SL (1.5×ATR) / TP (2.5×ATR) / 24 bar zaman çıkışı. Aynı barda ikisi de değerse SL öncelikli (kötümser).""" - A = ind["_arr"] - o, h, l, c, atr_a = A["o"], A["h"], A["l"], A["c"], A["atr"] - n = len(c) - trades = [] - i = start - while i <= n - 2: - s = scores[i - start]["score"] - a = atr_a[i] - if abs(s) < threshold or not (a > 0): - i += 1 - continue - d = 1 if s > 0 else -1 - entry = o[i + 1] - lv = scores[i - start].get("levels") if DYNAMIC_EXITS else None - sl_m, tp_m = (lv["sl"], lv["tp"]) if lv and lv["dir"] == d else (SL_ATR, TP_ATR) - risk = sl_m * a - sl, tp = entry - d * risk, entry + d * tp_m * a - last = min(n - 1, i + 1 + MAX_HOLD_BARS) - exit_i, exit_px, why = None, None, None - for j in range(i + 1, last + 1): - hit_sl = (l[j] <= sl) if d > 0 else (h[j] >= sl) - hit_tp = (h[j] >= tp) if d > 0 else (l[j] <= tp) - if hit_sl: - exit_i, exit_px, why = j, sl, "SL" - break - if hit_tp: - exit_i, exit_px, why = j, tp, "TP" - break - if exit_i is None: - if last == i + 1 + MAX_HOLD_BARS: - exit_i, exit_px, why = last, c[last], "ZAMAN" - else: - break # veri bitti, işlem hâlâ açık — sonuçlandırılamaz - pnl = d * (exit_px - entry) / entry - 2 * FEE_PER_SIDE - r_mult = pnl / (risk / entry) - trades.append({"signal_i": i, "entry_i": i + 1, "exit_i": exit_i, "dir": d, "entry": entry, - "exit": exit_px, "pnl": pnl, "r": r_mult, "why": why, "score": s, - "sl_atr": sl_m, "tp_atr": tp_m, "dyn": bool(lv), "risk_pct": risk / entry}) - i = exit_i - return trades - - -def summarize_trades(trades, df, start): - if len(trades) < 3: - return None - n = len(df) - pnl = np.array([t["pnl"] for t in trades]) - r = np.array([t["r"] for t in trades]) - wins, losses = pnl[pnl > 0], pnl[pnl <= 0] - gross_loss = abs(losses.sum()) - pf = float(wins.sum() / gross_loss) if gross_loss > 0 else (99.0 if wins.sum() > 0 else 0.0) - win_rate = len(wins) / len(pnl) - win_r, loss_r = r[r > 0], r[r <= 0] - avg_win_r = float(win_r.mean()) if len(win_r) else 0.0 - avg_loss_r = float(abs(loss_r.mean())) if len(loss_r) else 0.0 - sqn = float(np.sqrt(min(len(r), 100)) * r.mean() / r.std()) if r.std() > 0 else 0.0 - - mult = np.ones(n - start) - for t in trades: - mult[t["exit_i"] - start] *= (1 + t["pnl"]) - equity = np.cumprod(mult) - peak = np.maximum.accumulate(equity) - max_dd = float(((equity - peak) / peak).min()) - c = df["c"].to_numpy() - bh = c[start:] / c[start] - - streak = best = 0 - for p in pnl: - streak = streak + 1 if p <= 0 else 0 - best = max(best, streak) - - def side(dirn): - sub = [t for t in trades if t["dir"] == dirn] - return {"n": len(sub), "win_rate": (sum(1 for t in sub if t["pnl"] > 0) / len(sub)) if sub else None} - - stats = { - "n": len(trades), "win_rate": win_rate, "profit_factor": pf, "expectancy": float(pnl.mean()), - "avg_r": float(r.mean()), "sqn": sqn, "max_dd": max_dd, "total_return": float(equity[-1] - 1), - "bh_return": float(bh[-1] - 1), "max_consec_loss": best, "avg_win_r": avg_win_r, - "avg_loss_r": avg_loss_r, "long": side(1), "short": side(-1), - } - return stats, equity, bh - - -class _LiteProphet: - """'prophet' kurulu değilse yedek. Prophet API'siyle uyumlu (fit / make_future_dataframe / predict): parçalı-doğrusal trend - (ridge'li 10 changepoint) + Fourier gün-içi/haftalık mevsimsellik; aralık = artık sapma · √(1 + 0.15·h). Meta Prophet DEĞİLDİR.""" - def __init__(self, changepoint_prior_scale=0.05, interval_width=0.8, daily_seasonality=False, - weekly_seasonality=False, yearly_seasonality=False, uncertainty_samples=0, **kw): - self.cps, self.w, self.unc = float(changepoint_prior_scale), float(interval_width), uncertainty_samples - self.daily, self.weekly = bool(daily_seasonality), bool(weekly_seasonality) - - @staticmethod - def _sec(ds): - return pd.to_datetime(ds).values.astype("datetime64[s]").astype(np.int64).astype(float) - - def _X(self, ts): - tn = (ts - self.t0) / self.T - cols = [np.ones_like(tn), tn] + [np.maximum(0.0, tn - c) for c in self.cp] - for on, per in ((self.daily, 86400.0), (self.weekly, 7 * 86400.0)): - if on: - for k in (1, 2, 3): - a = 2 * np.pi * k * (ts - self.t0) / per - cols += [np.sin(a), np.cos(a)] - return np.column_stack(cols) - - def fit(self, df): - ts, y = self._sec(df["ds"]), df["y"].to_numpy(dtype=float) - self.t0, self.T = ts[0], max(ts[-1] - ts[0], 1.0) - self.cp = list(np.linspace(0.0, 0.8, 12)[1:-1]) - self.ym, self.ys = float(y.mean()), (float(y.std()) or 1.0) - z = (y - self.ym) / self.ys - X = self._X(ts) - b0 = np.linalg.lstsq(X, z, rcond=None)[0] - s2 = max(float(np.mean((z - X @ b0) ** 2)), 1e-6) - pen = np.zeros(X.shape[1]); pen[2:2 + len(self.cp)] = s2 / (self.cps ** 2) # Gauss önsel sd = changepoint_prior_scale - self.beta = np.linalg.solve(X.T @ X + np.diag(pen) + 1e-6 * np.eye(X.shape[1]), X.T @ z) - self.sd = float(np.std((z - X @ self.beta) * self.ys)) - self.last, self.step, self.ds = ts[-1], float(np.median(np.diff(ts))) if len(ts) > 1 else 3600.0, pd.to_datetime(df["ds"]) - return self - - def make_future_dataframe(self, periods, freq="D", include_history=True): - fut = pd.date_range(self.ds.iloc[-1], periods=periods + 1, freq=freq)[1:] - return pd.DataFrame({"ds": list(self.ds) + list(fut) if include_history else list(fut)}) - - def predict(self, fut): - ts = self._sec(fut["ds"]) - yh = (self._X(ts) @ self.beta) * self.ys + self.ym - out = {"ds": pd.to_datetime(fut["ds"]).values, "yhat": yh} - if self.unc and self.unc > 0: - z = NormalDist().inv_cdf(0.5 + self.w / 2) - h = np.maximum((ts - self.last) / self.step, 0.0) - sdh = self.sd * np.sqrt(1.0 + 0.15 * h) - out["yhat_lower"], out["yhat_upper"] = yh - z * sdh, yh + z * sdh - return pd.DataFrame(out) - - -def _prophet_cls(): - if Prophet is not None: - return Prophet - return _LiteProphet if PROPHET_FALLBACK_LITE else None - - -def _prophet_name(): - return "Prophet" if Prophet is not None else ("Prophet-lite (yerleşik)" if PROPHET_FALLBACK_LITE else "yok") - - -def _prophet_forecast(ds, y, bar, horizon, intervals): - """Prophet'i (ds, y) üzerinde eğitip gelecek 'horizon' bar için tahmin DataFrame'i döndürür (yhat [, yhat_lower, yhat_upper]).""" - span = (ds.iloc[-1] - ds.iloc[0]).total_seconds() / 86400.0 - m = _prophet_cls()(changepoint_prior_scale=PROPHET_CPS, interval_width=PROPHET_INTERVAL, - daily_seasonality=bool(bar not in ("1D", "1W") and span >= 2), - weekly_seasonality=bool(bar != "1W" and span >= 14), yearly_seasonality=False, - uncertainty_samples=PROPHET_SAMPLES if intervals else 0) - m.fit(pd.DataFrame({"ds": ds, "y": y})) - return m.predict(m.make_future_dataframe(periods=horizon, freq=BAR_FREQ.get(bar, "1h"), include_history=False)) - - -def prophet_risk_inputs(df, ind, bar): - """İki Prophet modeli (yön-bağımsız, önbelleğe uygun): - 1) log(kapanış) → ufuktaki beklenen getiri mu_h + fiyat bandı; ölçek = son 100 barın gerçekleşmiş volatilitesi·√H - 2) log(ATR/fiyat) → ufuktaki ort. volatilite / şimdiki volatilite (vol_ratio) — Prophet'in gün-içi mevsimselliği volatilite örüntüsünü yakalar - Dönüş: dict veya {"error": ...} (hata yutulur; motor Prophet'siz devam eder).""" - if _prophet_cls() is None: - return {"error": "prophet kurulu değil (pip install prophet) ve yedek kapalı"} - if len(df) < PROPHET_MIN_BARS: - return {"error": f"Prophet için en az {PROPHET_MIN_BARS} mum gerekir (var: {len(df)})"} - H = PROPHET_HORIZON - try: - d = df.tail(PROPHET_MAX_BARS).reset_index(drop=True) - ds = pd.to_datetime(d["ts"]) - ly = np.log(d["c"].to_numpy(dtype=float)) - fc = _prophet_forecast(ds, ly, bar, H, True) - out = {"mu_h": float(fc["yhat"].iloc[-1] - ly[-1]), "price_fc": float(np.exp(fc["yhat"].iloc[-1])), - "lo": float(np.exp(fc["yhat_lower"].iloc[-1])), "hi": float(np.exp(fc["yhat_upper"].iloc[-1])), - "horizon": H, "vol_ratio": None, "vol_now": None, "vol_fc": None} - rets = np.diff(ly)[-100:] - out["sig_h"] = float(np.std(rets, ddof=1) * math.sqrt(H)) if len(rets) > 5 else float("nan") - av = (ind["atr"].to_numpy(dtype=float) / df["c"].to_numpy(dtype=float))[-len(d):] - ok = np.isfinite(av) & (av > 0) - if ok.sum() >= 60: - fv = _prophet_forecast(ds[ok].reset_index(drop=True), np.log(av[ok]), bar, H, False) - out["vol_now"], out["vol_fc"] = float(av[ok][-1]), float(np.exp(fv["yhat"]).mean()) - out["vol_ratio"] = out["vol_fc"] / out["vol_now"] - return out - except Exception as e: # Prophet/cmdstan hatası canlı sinyali düşürmemeli - return {"error": f"Prophet hatası: {str(e)[:90]}"} - - -def prophet_risk_scale(pf, d): - """Prophet çıktısı + sinyal yönü → risk çarpanı (≤ 1). Yalnızca KÜÇÜLTÜR; yön çevirmez. - yön çarpanı : P(lehte) = Φ(d·mu_h / σ_h). ≥ PROPHET_PFAV_FULL → 1; PROPHET_PFAV_VETO'ya doğru doğrusal azalır; altında VETO (V6) - vol çarpanı : min(1, 1 / vol_ratio) — ufukta volatilite artacaksa pozisyon küçülür (volatilite hedefleme); azalacaksa büyütmez.""" - if not pf or pf.get("error"): - return {"mult": 1.0, "veto": None, "p_fav": None, "dir_scale": 1.0, "vol_scale": 1.0, "error": (pf or {}).get("error"), "inputs": pf} - lo_m = PROPHET_MULT_MIN - sig, p_fav, dir_scale = pf.get("sig_h"), None, 1.0 - if sig and np.isfinite(sig) and sig > 0: - z = d * pf["mu_h"] / sig - p_fav = 0.5 * (1.0 + math.erf(z / math.sqrt(2.0))) - dir_scale = float(np.clip(lo_m + (1 - lo_m) * (p_fav - PROPHET_PFAV_VETO) / (PROPHET_PFAV_FULL - PROPHET_PFAV_VETO), lo_m, 1.0)) - vr = pf.get("vol_ratio") - vol_scale = float(np.clip(1.0 / vr, lo_m, 1.0)) if vr and vr > 0 else 1.0 - veto = f"V6 Prophet tahmini sinyale ters (P(lehte) %{p_fav * 100:.0f})" if (p_fav is not None and p_fav < PROPHET_PFAV_VETO) else None - mult = 0.0 if veto else float(np.clip(dir_scale * vol_scale, lo_m, 1.0)) - return {"mult": mult, "veto": veto, "p_fav": p_fav, "dir_scale": dir_scale, "vol_scale": vol_scale, "error": None, "inputs": pf} - - -_PF_CACHE, _PF_LOCK = {}, threading.Lock() - - -def prophet_backtest_attach(trades, df, ind, bar, symbol=None): - """Her işlemin sinyal barı için NEDENSEL Prophet çarpanı/vetosu hesaplar ve t["prophet"]'a yazar. - Nedensellik: sinyal barı i için eğitim noktası f ≤ i (f = i − (mutlak_bar_no mod STEP)), model YALNIZCA ≤ f verisiyle eğitilir; - f sabit bar numarasına hizalı olduğundan sonuçlar sonraki yenilemelerde yeniden kullanılır. Tahmin f'den ileri uzatılır: - mu_h = yhat[f → i+H] − log(kapanış_i) (i anında bilinen kapanışa göre) · vol_ratio = ort(yhat_vol[i+1…i+H]) / ATR%_i - Not: canlı hesaba göre model en çok STEP−1 bar 'bayat' olabilir (backtest hafifçe kötümser). Dönüş: özet dict veya None.""" - H, R = PROPHET_HORIZON, max(1, int(PROPHET_BT_STEP)) - base_info = {"fits": 0, "covered": 0, "errors": 0, "step": R, "n": len(trades or []), "reason": None, "error": None} - if not trades: - return dict(base_info, reason="backtest penceresinde işlem yok") - if _prophet_cls() is None: - return dict(base_info, reason="prophet kurulu değil ve yedek kapalı (pip install prophet)") - ts = pd.to_datetime(df["ts"]) - dts = np.diff(ts.values.astype("datetime64[s]").astype(np.int64)) - secs = int(np.median(dts)) if len(dts) else 3600 - absi = ts.values.astype("datetime64[s]").astype(np.int64) // max(secs, 1) - c = df["c"].to_numpy(dtype=float) - ly = np.log(c) - av = ind["atr"].to_numpy(dtype=float) / c - need = {} - for t in trades: - i = t["signal_i"] - f = i - int(absi[i] % R) - if f + 1 >= PROPHET_MIN_BARS: - need[i] = f - - def fit_point(f): - key = ("pf", symbol, bar, str(ts.iloc[f]), PROPHET_MAX_BARS, R, H) - with _PF_LOCK: - hit = _PF_CACHE.get(key) - if hit is not None: - return hit - try: - lo = max(0, f + 1 - PROPHET_MAX_BARS) - dsx = ts.iloc[lo:f + 1].reset_index(drop=True) - fp = _prophet_forecast(dsx, ly[lo:f + 1], bar, R + H, False)["yhat"].to_numpy(dtype=float) - a = av[lo:f + 1] - ok = np.isfinite(a) & (a > 0) - fv = None - if ok.sum() >= 60: - fv = np.exp(_prophet_forecast(dsx[ok].reset_index(drop=True), np.log(a[ok]), bar, R + H, False)["yhat"].to_numpy(dtype=float)) - res = {"fp": fp, "fv": fv} - except Exception as e: - res = {"error": f"{type(e).__name__}: {str(e)[:90]}"} - with _PF_LOCK: - _PF_CACHE[key] = res - if len(_PF_CACHE) > 400: - for k in list(_PF_CACHE)[:100]: - _PF_CACHE.pop(k, None) - return res - - fits = {} - uniq = sorted(set(need.values())) - if uniq: - with ThreadPoolExecutor(max_workers=max(1, int(PROPHET_BT_WORKERS))) as ex: - futs = {ex.submit(fit_point, f): f for f in uniq} - for fu in as_completed(futs): - fits[futs[fu]] = fu.result() - covered = errors = 0 - first_err = next((r["error"] for r in fits.values() if r.get("error")), None) - for t in trades: - i = t["signal_i"] - f = need.get(i) - r = fits.get(f) if f is not None else None - if not r or r.get("error"): - errors += 1 if (r and r.get("error")) else 0 - continue - k = i - f - rets = np.diff(ly[max(0, i - 100):i + 1]) - pf = {"mu_h": float(r["fp"][k + H - 1] - ly[i]), - "sig_h": float(np.std(rets, ddof=1) * math.sqrt(H)) if len(rets) > 5 else float("nan"), - "vol_ratio": (float(r["fv"][k:k + H].mean() / av[i]) if (r["fv"] is not None and np.isfinite(av[i]) and av[i] > 0) else None)} - t["prophet"] = prophet_risk_scale(pf, t["dir"]) - covered += 1 - reason = None - if not uniq: - reason = f"işlemler Prophet için çok erken (en az {PROPHET_MIN_BARS} mum gerekir, eldeki: {len(df)})" - elif covered == 0 and first_err: - reason = "eğitim hatası: " + first_err - return dict(base_info, fits=len(uniq), covered=covered, errors=errors, reason=reason, error=first_err) - - -def apply_position_sizing(trades, risk_base=None, max_alloc=None, use_prophet=True): - """PROPHET-ONLY pozisyon boyutlandırma (başka risk katmanı yok). Her işlem için: - risk = risk_base × Prophet çarpanı (≤ 1); Prophet VETO (V6: trend sinyale ters) → işlem açılmaz ('gölge', sonucu izlenir). - Prophet verisi yoksa çarpan 1 (yani sabit risk_base). Pozisyon = min(max_alloc, risk / işlem_riski). - Kıyas: aynı işlemlerde Prophet'siz sabit %risk_base. Dönüş: dict veya None.""" - risk_base = SIZE_RISK_BASE if risk_base is None else risk_base - max_alloc = SIZE_MAX_ALLOC if max_alloc is None else max_alloc - if not trades: - return None - eq = peak = feq = fpeak = 1.0 - dd_max = fdd_max = 0.0 - allocs, sized_pnl, flat_pnl, curve, vetoes = [], [], [], [], {} - for t in sorted(trades, key=lambda x: x["signal_i"]): - rp = t.get("risk_pct") or (abs(t["pnl"] / t["r"]) if t["r"] else 0.0) - pp = t.get("prophet") if use_prophet else None - why, mult = None, 1.0 - if pp: - if pp.get("veto"): - why = pp["veto"] - else: - mult = pp["mult"] - alloc = 0.0 if (why or rp <= 0) else min(max_alloc, risk_base * mult / rp) - if why: - key = why.split(" (")[0] - vetoes[key] = vetoes.get(key, 0) + 1 - fl = min(max_alloc, risk_base / rp) if rp > 0 else 0.0 - ret = alloc * t["pnl"] - eq *= 1 + ret - feq *= 1 + fl * t["pnl"] - peak, fpeak = max(peak, eq), max(fpeak, feq) - dd_max, fdd_max = max(dd_max, 1 - eq / peak), max(fdd_max, 1 - feq / fpeak) - allocs.append(alloc); sized_pnl.append(ret); flat_pnl.append(fl * t["pnl"]); curve.append(eq) - - def pf(x): - x = np.asarray(x) - gl = abs(x[x < 0].sum()) - return float(x[x > 0].sum() / gl) if gl > 0 else (99.0 if x[x > 0].sum() > 0 else 0.0) - - pr_diag = None - cov = [t for t in trades if use_prophet and t.get("prophet")] - if len(cov) >= 5: # Prophet işe yarıyor mu? veto edilenler gerçekten daha mı kötüydü? - vet = [t["r"] for t in cov if t["prophet"]["veto"]] - kept = [t["r"] for t in cov if not t["prophet"]["veto"]] - pr_ = [(t["prophet"]["p_fav"], t["r"]) for t in cov if t["prophet"]["p_fav"] is not None] - corr = None - if len(pr_) >= 8: - a_ = np.asarray(pr_) - if a_[:, 0].std() > 0 and a_[:, 1].std() > 0: - corr = float(np.corrcoef(a_[:, 0], a_[:, 1])[0, 1]) - pr_diag = {"n": len(cov), "n_veto": len(vet), "r_veto": float(np.mean(vet)) if vet else None, - "r_kept": float(np.mean(kept)) if kept else None, "corr": corr} - a = np.asarray(allocs) - return {"total_return": eq - 1, "max_dd": -dd_max, "pf": pf(sized_pnl), "n_active": int((a > 0).sum()), "n": len(a), - "mean_alloc": float(a[a > 0].mean()) if (a > 0).any() else 0.0, "curve": curve, "vetoes": vetoes, - "prophet": pr_diag, "flat_return": feq - 1, "flat_max_dd": -fdd_max, "flat_pf": pf(flat_pnl)} - - -def run_backtest(df, ind, start, scores, bar=None, symbol=None, use_prophet=False): - n = len(df) - if n - start < 20: - return None - acc = directional_accuracy(df, start, scores) - trades = simulate_trades(df, ind, start, scores) - pinfo = prophet_backtest_attach(trades, df, ind, bar, symbol) if (use_prophet and PROPHET_BACKTEST) else None - summ = summarize_trades(trades, df, start) - bt = {"start": start, "acc": acc, "trades": trades, "stats": None, "equity": None, "bh": None, - "sized": apply_position_sizing(trades), "prophet": pinfo} - if summ: - bt["stats"], bt["equity"], bt["bh"] = summ - return bt - - -# --------------------------------------------------------------------------- -# CANLI KATMAN — üst zaman dilimleri, emir defteri, funding (backtest EDİLMEZ) -# --------------------------------------------------------------------------- -def _mtf_score(symbol, bar, closed_only): - hdf = fetch_candles(symbol, bar, 300) - if closed_only: - hdf = hdf[hdf["confirm"] == 1].reset_index(drop=True) - hind = compute_indicators(hdf) - r = _score_at(hind, len(hdf) - 1) - return {"bar": bar, "score": r["score"], "trend": r["trend"]} - - -def _fetch_live_extras(symbol, bar, mtf, micro, closed_only): - """MTF / emir defteri / funding çağrılarını PARALEL yapar; hata veren parça sessizce atlanır.""" - tasks = {} - with ThreadPoolExecutor(max_workers=4) as ex: - if mtf and symbol and bar: - b1 = MTF_MAP.get(bar) - if b1 and b1 != bar: - tasks["mtf1"] = ex.submit(_mtf_score, symbol, b1, closed_only) - b2 = MTF_MAP.get(b1) - if b2 and b2 not in (b1, bar): - tasks["mtf2"] = ex.submit(_mtf_score, symbol, b2, closed_only) - if micro and symbol: - tasks["ob"] = ex.submit(fetch_orderbook, symbol, 20) - tasks["fund"] = ex.submit(fetch_funding_rate, symbol) - out = {} - for k, f in tasks.items(): - try: - out[k] = f.result() - except Exception: - out[k] = None - return out - - -def build_trade_plan(price, direction, atr_v, supports, resists, acct=None, risk_pct=None, smc_level=None, levels=None): - """Stop önceliği: 1) SMC seviyesi (order block / likidite avı ekstremi arkası), 2) yapısal S/R arkası, 3) ATR stop. - Aday stop, fiyata 0.8–3.0 ATR uzaklıktaysa kullanılır. 3 kademeli hedef + hesap büyüklüğüne göre pozisyon boyutu.""" - d = direction - use_dyn = bool(levels) and levels.get("dir") == d - sl_m = levels["sl"] if use_dyn else SL_ATR - stop = price - d * sl_m * atr_v - stop_type = f"KNN dinamik ({sl_m:.2f}×ATR)" if use_dyn else f"ATR × {SL_ATR}" - cands = [] - if smc_level is not None: - cands.append((smc_level - d * 0.25 * atr_v, "SMC (OB / likidite arkası)")) - if d > 0 and supports: - cands.append((supports[0][0] - 0.25 * atr_v, "Yapısal (S/R arkası)")) - elif d < 0 and resists: - cands.append((resists[0][0] + 0.25 * atr_v, "Yapısal (S/R arkası)")) - for cand, ctype in cands: - dist = abs(price - cand) - if 0.8 * atr_v <= dist <= 3.0 * atr_v and (cand < price if d > 0 else cand > price): - stop, stop_type = cand, ctype - break - risk = abs(price - stop) - if use_dyn and risk > 0: - tp_r = [max(levels[k] * atr_v / risk, 0.5) for k in ("tp", "tp2", "tp3")] - tp_r = [tp_r[0], max(tp_r[1], tp_r[0] * 1.1), max(tp_r[2], tp_r[0] * 1.2)] - tp_r[2] = max(tp_r[2], tp_r[1] * 1.1) - tp_r = [round(m, 2) for m in tp_r] - else: - tp_r = TP_R_MULTIPLES - plan = { - "direction": "LONG" if d > 0 else "SHORT", "entry": price, "stop_loss": stop, "stop_type": stop_type, - "risk_dist": risk, "risk_frac": risk / price, - "targets": [(price + d * risk * m, m) for m in tp_r], "sizing": None, - } - if acct and acct > 0 and risk_pct and risk_pct > 0 and risk > 0: - risk_amt = acct * risk_pct / 100.0 - qty = risk_amt / risk - notional = qty * price - plan["sizing"] = {"risk_amount": risk_amt, "qty": qty, "notional": notional, "leverage": notional / acct} - return plan - - -def compute_confidence(score, agree, mtf_state, hist_q, vol_q): - """Güven = bileşenlerin EŞİT ağırlıklı ortalaması (bileşen ağırlığı yok).""" - mag = min(abs(score) / 5.0, 1.0) - parts = {"Teyit Sayısı": mag, "Teyit Uyumu": agree, "MTF Yapı Teyidi": mtf_state, - "Tarihsel Kenar": hist_q, "Volatilite Rejimi": vol_q} - conf = 100.0 * sum(parts.values()) / len(parts) - return min(conf, 100.0), parts - - -def grade_from_conf(conf): - for thr, g, col in ((75, "A+", "#3ddc97"), (62, "A", "#7fe6b8"), (50, "B", "#f2b84b"), (38, "C", "#ff9aa8")): - if conf >= thr: - return g, col - return "D", "#ff5c72" - - -def generate_signal(df, symbol=None, bar=None, mtf=True, micro=True, closed_only=False, - acct=None, risk_pct=None): - ind = compute_indicators(df, xgb=XGB_ENABLED) - n = len(df) - start, scores = compute_score_series(ind, n) - base = _score_at(ind, n - 1, verbose=True) - score, price = base["score"], base["price"] - reasons, contribs = list(base["reasons"]), list(base["contribs"]) - up_c, dn_c = "#3ddc97", "#ff5c72" - tags = [] - if base["trend"] != 0: - tags.append(("YAPI ▲" if base["trend"] > 0 else "YAPI ▼", up_c if base["trend"] > 0 else dn_c)) - for t, d in base["tags"]: - tags.append((t, up_c if d > 0 else dn_c)) - - def note(text): - """Bilgi notu — skoru DEĞİŞTİRMEZ (ağırlık / ek puan yok).""" - reasons.append(("flat", text)) - - # --- Canlı ekstralar (paralel çekim) --- - extras = _fetch_live_extras(symbol, bar, mtf, micro, closed_only) - sgn = 1 if score > 0 else (-1 if score < 0 else 0) - - # --- 2 kademeli MTF yapı teyidi (üst zaman diliminin piyasa yapısı sinyal yönüyle uyumlu mu?) --- - mtf_info, align_flags = [], [] - for key in ("mtf1", "mtf2"): - m = extras.get(key) - if not m: - continue - aligned = None - if sgn != 0 and m["trend"] != 0: - aligned = bool(m["trend"] == sgn) - align_flags.append(aligned) - nm = "yükseliş" if m["trend"] > 0 else "düşüş" - note(f"{m['bar']} piyasa yapısı {nm} — sinyalle " - + ("uyumlu ✓" if aligned else "ÇELİŞKİLİ ⚠ (GÜÇLÜ sinyal verilmez)")) - mtf_info.append({"bar": m["bar"], "score": round(m["score"], 2), "trend": m["trend"], "aligned": aligned}) - mtf_state = (sum(1.0 for a in align_flags if a) / len(align_flags)) if align_flags else 0.5 - mtf_opposed = any(a is False for a in align_flags) - - # --- Emir defteri dengesizliği (bilgi) --- - imb = None - ob = extras.get("ob") - if ob and ob.get("bids") and ob.get("asks"): - bw = sum(float(x[1]) / (k + 1) for k, x in enumerate(ob["bids"])) - aw = sum(float(x[1]) / (k + 1) for k, x in enumerate(ob["asks"])) - if bw + aw > 0: - imb = (bw - aw) / (bw + aw) - if abs(imb) >= 0.2: - note(f"Emir defterinde {'alış' if imb > 0 else 'satış'} baskısı (dengesizlik {imb * 100:+.0f}%)") - - # --- Likidite duvarları (bilgi) --- - walls = None - atr_w = float(ind["atr"].iloc[-1]) - if ob and ob.get("bids") and ob.get("asks") and atr_w > 0: - sup_w, res_w = book_walls(ob, price, atr_w) - walls = {"support": sup_w, "resist": res_w} - if sup_w: - note(f"Yakında büyük alış duvarı {fmt_price(sup_w[0])} (medyanın {sup_w[1]:.1f}×'i) — destek olabilir") - if res_w: - note(f"Yakında büyük satış duvarı {fmt_price(res_w[0])} (medyanın {res_w[1]:.1f}×'i) — direnç olabilir") - - # --- Funding rate (bilgi) --- - fr = extras.get("fund") - if fr is not None and abs(fr) > 0.0002: - side = "long" if fr > 0 else "short" - note(f"Funding {fr * 100:+.3f}% — {side} tarafı kalabalık ({'tasfiye riski' if fr > 0 else 'sıkışma potansiyeli'})") - - # --- Destek / direnç (bilgi + stop yerleşimi) --- - atr_v = float(ind["atr"].iloc[-1]) - supports, resists = find_sr_levels(df, atr_v) - if atr_v > 0 and sgn > 0 and resists and resists[0][1] >= 2 and (resists[0][0] - price) / atr_v <= 1.0: - lv, tc = resists[0] - note(f"Dikkat: yakın direnç {fmt_price(lv)} ({tc} temas, {(lv - price) / atr_v:.1f} ATR) — long hedeflerini sınırlayabilir") - elif atr_v > 0 and sgn < 0 and supports and supports[0][1] >= 2 and (price - supports[0][0]) / atr_v <= 1.0: - lv, tc = supports[0] - note(f"Dikkat: yakın destek {fmt_price(lv)} ({tc} temas, {(price - lv) / atr_v:.1f} ATR) — short hedeflerini sınırlayabilir") - - # --- Konsensüs + sınıflandırma --- - direction = 1 if score > 0 else -1 - tot = base["bull_w"] + base["bear_w"] - agree = ((base["bull_w"] if direction > 0 else base["bear_w"]) / tot) if tot > 0 else 0.5 - if abs(score) >= STRONG_THRESHOLD and agree >= 0.65 and not mtf_opposed: - signal, color = ("GÜÇLÜ AL", "#3ddc97") if score > 0 else ("GÜÇLÜ SAT", "#ff5c72") - elif abs(score) >= SIGNAL_THRESHOLD: - signal, color = ("AL", "#7fe6b8") if score > 0 else ("SAT", "#ff9aa8") - if abs(score) >= STRONG_THRESHOLD: - note(f"Teyit sayısı güçlü bölgede ancak uyum (%{agree * 100:.0f}) veya MTF yapı filtresini geçemedi — GÜÇLÜ yerine normal sinyal verildi") - else: - signal, color = "BEKLE / NÖTR", "#f2b84b" - is_neutral = signal == "BEKLE / NÖTR" - - # --- Tarihsel doğrulama --- - backtest = run_backtest(df, ind, start, scores, bar=bar, symbol=symbol, - use_prophet=bool(PROPHET_ENABLED and mtf)) - hist_q = 0.4 - if backtest: - qs = [] - acc = backtest["acc"] - if acc: - p_shrunk = (acc["correct"] + 5) / (acc["n_signals"] + 10) # Bayes büzülmesi (önsel %50) - qs.append(min(max((p_shrunk - 0.45) / 0.20, 0.0), 1.0)) - st = backtest["stats"] - if st: - qs.append(min(max((st["profit_factor"] - 0.8) / 0.8, 0.0), 1.0)) - if qs: - hist_q = float(np.mean(qs)) - rank = base["atr_rank"] - vol_q = 1.0 if 0.10 <= rank <= 0.90 else 0.5 - if rank > 0.90: - note(f"Volatilite aşırı yüksek (ATR yüzdeliği %{rank * 100:.0f}) — stoplar geniş tutulmalı, güven düşürüldü") - conf, conf_parts = compute_confidence(score, agree, mtf_state, hist_q, vol_q) - grade, grade_color = ("—", "#7c8797") if is_neutral else grade_from_conf(conf) - - pf_live = None - if PROPHET_ENABLED and mtf: - pkey = ("prophet", symbol, bar, str(df["ts"].iloc[-1]), len(df)) - pf_live = _cached(pkey, 180, lambda: prophet_risk_inputs(df, ind, bar)) - plan, eng = None, None - if not is_neutral and atr_v > 0: - ref = base["stop_ref"]["long" if direction > 0 else "short"] - eff_risk = risk_pct - if PROPHET_ENABLED and risk_pct and risk_pct > 0 and pf_live is not None: - # RİSK MOTORU (yalnızca Prophet): risk = kullanıcı riski × Prophet çarpanı; Prophet VETO (V6) varsa risk 0 (işlem açılmaz). - prp = prophet_risk_scale(pf_live, direction) - veto = prp["veto"] - eff_risk = 0.0 if veto else risk_pct * prp["mult"] - eng = {"prophet": prp, "risk_pct": eff_risk, "user_cap": risk_pct, "veto": veto} - plan = build_trade_plan(price, direction, atr_v, supports, resists, acct, eff_risk, smc_level=ref, levels=base.get("levels")) - - return { - "engine": eng, "prophet_live": pf_live, "xgb_p": base.get("xgb_p"), "signal": signal, "score": round(score, 2), "confidence": round(conf, 1), "grade": grade, - "grade_color": grade_color, "color": color, "reasons": reasons, "contribs": contribs, - "indicators": ind, "price": price, "mtf": mtf_info, "plan": plan, - "backtest": backtest, "regime": base["regime"], "trend": base["trend"], "pd": base["pd"], - "atr_rank": rank, "agree": agree, "bull_w": base["bull_w"], "bear_w": base["bear_w"], - "conf_parts": conf_parts, "tags": tags, "funding": fr, "imbalance": imb, - "supports": supports, "resists": resists, "atr": atr_v, "walls": walls, - "smc": ind["smc_zones"], - "vp": {"poc": float(ind["vp_poc"].iloc[-1]), "val": float(ind["vp_val"].iloc[-1]), - "vah": float(ind["vp_vah"].iloc[-1]), "heat": float(ind["vp_heat"].iloc[-1])}, - } - - -# --------------------------------------------------------------------------- -# RISK ANALYTICS -# --------------------------------------------------------------------------- - - - - - - - - - - - - - - - - - - -def risk_metrics(df, bar): - ret = df["c"].pct_change().dropna() - if len(ret) < 5: - return None - periods = BAR_TO_PERIODS_PER_YEAR.get(bar, 365) - vol_annual = ret.std() * np.sqrt(periods) - mean_annual = ret.mean() * periods - sharpe = mean_annual / vol_annual if vol_annual > 0 else 0.0 - cum = (1 + ret).cumprod() - peak = cum.cummax() - dd = (cum - peak) / peak - var95 = np.percentile(ret, 5) - tail = ret[ret <= var95] - cvar95 = tail.mean() if len(tail) else var95 - return { - "volatility_annual": vol_annual, "return_annual": mean_annual, "sharpe": sharpe, - "max_drawdown": dd.min(), "current_dd": dd.iloc[-1], - "var95_bar": var95, "cvar95_bar": cvar95, - } - - -# --------------------------------------------------------------------------- -# FORMAT HELPERS -# --------------------------------------------------------------------------- -def _isnan(v): - return v is None or (isinstance(v, float) and math.isnan(v)) - - -def fmt_price(v): - if _isnan(v): - return "—" - av = abs(v) - if av >= 1000: - return f"{v:,.2f}" - if av >= 1: - return f"{v:,.4f}" - if av >= 0.01: - return f"{v:.6f}" - return f"{v:.8f}" - - -def fmt_pct(v): - if _isnan(v): - return "—" - sign = "+" if v > 0 else "" - return f"{sign}{v * 100:.2f}%" - - -def fmt_large(v): - if _isnan(v): - return "—" - av = abs(v) - if av >= 1e9: - return f"{v / 1e9:.2f}B" - if av >= 1e6: - return f"{v / 1e6:.2f}M" - if av >= 1e3: - return f"{v / 1e3:.2f}K" - return f"{v:.2f}" - - -def metric_card(label, value, sub="", tone="neutral"): - tone_class = {"up": "mc-up", "down": "mc-down", "neutral": "mc-neutral"}.get(tone, "mc-neutral") - sub_html = f'
{sub}
' if sub else "" - return (f'
{label}
' - f'
{value}
{sub_html}
') - - -def section(title): - return f'
{title}
' - - -def cards_row(cards): - return f'
{"".join(cards)}
' if cards else "" - - -def error_box(msg): - return f'
⚠ {msg}
' - - -# --------------------------------------------------------------------------- -# CHART BUILDERS -# --------------------------------------------------------------------------- -def _base_layout(fig, title, height): - fig.update_layout( - template="plotly_dark", height=height, margin=dict(l=10, r=10, t=40, b=10), - paper_bgcolor=CHART_BG, plot_bgcolor=CHART_BG, title=title, - legend=dict(orientation="h", y=1.05, font=dict(size=10)), - font=dict(family="IBM Plex Mono, monospace", size=11), - ) - return fig - - -def _empty_fig(msg, title="", height=220): - fig = go.Figure() - fig.add_annotation(text=msg, x=0.5, y=0.5, xref="paper", yref="paper", showarrow=False, - font=dict(size=13, color="#7c8797")) - fig.update_xaxes(visible=False) - fig.update_yaxes(visible=False) - return _base_layout(fig, title, height) - - -# --------------------------------------------------------------------------- -# 3D GRAFİK YARDIMCILARI (x = zaman/bar, y = fiyat, z = hacim yoğunluğu / katman yüksekliği) -# --------------------------------------------------------------------------- -_CUBE_I = [7, 0, 0, 0, 4, 4, 6, 6, 4, 0, 3, 2] -_CUBE_J = [3, 4, 1, 2, 5, 6, 5, 2, 0, 1, 6, 3] -_CUBE_K = [0, 7, 2, 3, 6, 7, 1, 1, 5, 5, 7, 6] - - -def _ticks3d(df, v0, n, k=6): - idx = sorted({int(round(x)) for x in np.linspace(v0, n - 1, k)}) - return idx, [df["ts"].iloc[i].strftime("%d.%m %H:%M") for i in idx] - - -def _scene3d(fig, df, v0, n, ylo, yhi, zmax, zlabel, x_pad=12, aspect=(2.8, 1.5, 0.85)): - tv, tt = _ticks3d(df, v0, n) - base = dict(backgroundcolor=CHART_BG, gridcolor="#232a35", zerolinecolor="#232a35", - showbackground=True, color="#7c8797") - fig.update_layout(scene=dict( - xaxis=dict(base, title="", tickmode="array", tickvals=tv, ticktext=tt, range=[v0, n - 1 + x_pad]), - yaxis=dict(base, title="Fiyat", range=[ylo, yhi]), - zaxis=dict(base, title=zlabel, range=[0, zmax], showticklabels=False), - aspectmode="manual", aspectratio=dict(x=aspect[0], y=aspect[1], z=aspect[2]), - camera=dict(eye=dict(x=1.55, y=-1.85, z=0.85)), - )) - - -def _candles3d(fig, df, v0, n, symbol): - """Mumlar z=0 zemin düzleminde: fitil ince, gövde kalın çizgi (Scatter3d).""" - idx = np.arange(v0, n) - o, h, l, c = (df[k].to_numpy(dtype=float)[v0:n] for k in ("o", "h", "l", "c")) - eps = max((h.max() - l.min()) * 0.002, 1e-12) - for up, col, nm in ((True, "#3ddc97", "Yükseliş mumu"), (False, "#ff5c72", "Düşüş mumu")): - m = (c >= o) if up else (c < o) - xw, yw, zw, xb, yb, zb = [], [], [], [], [], [] - for k in np.where(m)[0]: - x = float(idx[k]) - xw += [x, x, None]; yw += [float(l[k]), float(h[k]), None]; zw += [0.0, 0.0, None] - top, bot = float(max(o[k], c[k])), float(min(o[k], c[k])) - if top - bot < eps: - top = bot + eps - xb += [x, x, None]; yb += [bot, top, None]; zb += [0.0, 0.0, None] - if not xw: - continue - fig.add_trace(go.Scatter3d(x=xw, y=yw, z=zw, mode="lines", line=dict(color=col, width=2), - showlegend=False, hoverinfo="skip")) - fig.add_trace(go.Scatter3d(x=xb, y=yb, z=zb, mode="lines", line=dict(color=col, width=7), - name=f"{symbol} · {nm}", hoverinfo="skip")) - - -def _box3d(fig, x0, x1, y0, y1, z0, z1, color, opacity): - fig.add_trace(go.Mesh3d( - x=[x0, x0, x1, x1, x0, x0, x1, x1], y=[y0, y1, y1, y0, y0, y1, y1, y0], z=[z0, z0, z0, z0, z1, z1, z1, z1], - i=_CUBE_I, j=_CUBE_J, k=_CUBE_K, color=color, opacity=opacity, flatshading=True, - showlegend=False, hoverinfo="skip")) - - -def _rect3d(fig, x0, x1, y0, y1, color, opacity, z=0.0): - fig.add_trace(go.Mesh3d(x=[x0, x1, x1, x0], y=[y0, y0, y1, y1], z=[z, z, z, z], i=[0, 0], j=[1, 2], k=[2, 3], - color=color, opacity=opacity, showlegend=False, hoverinfo="skip")) - - -def _label3d(fig, x, y, z, text, color, size=9): - fig.add_trace(go.Scatter3d(x=[x], y=[y], z=[z], mode="text", text=[text], textposition="middle right", - textfont=dict(size=size, color=color), showlegend=False, hoverinfo="skip")) - - -def _hline3d(fig, x0, x1, y, color, width=2, dash="solid", label=None, z=0.0): - fig.add_trace(go.Scatter3d( - x=[x0, x1], y=[y, y], z=[z, z], mode="lines+text" if label else "lines", - line=dict(color=color, width=width, dash=dash), text=["", label] if label else None, - textposition="middle right", textfont=dict(size=9, color=color), showlegend=False, hoverinfo="skip")) - - -def _volume_surface3d(fig, df, v0, n, opacity=0.40): - """Hacim ısı haritası 3D yüzey: yükseklik = o fiyat seviyesindeki hacim yoğunluğu (0..1).""" - hh, ll = df["h"].to_numpy(dtype=float)[v0:n], df["l"].to_numpy(dtype=float)[v0:n] - edges = np.linspace(ll.min(), hh.max(), 41) - z = _volume_grid(hh, ll, df["vol"].to_numpy(dtype=float)[v0:n], edges).T # (dilim × bar) - z = z / max(float(z.max()), 1e-12) - fig.add_trace(go.Surface( - x=np.arange(v0, n), y=(edges[:-1] + edges[1:]) / 2, z=z, name="Hacim Isı Haritası", - colorscale=[[0, "#12171f"], [0.35, "#5b8cff"], [0.7, "#f2b84b"], [1, "#ff5c72"]], - opacity=opacity, showscale=False, hoverinfo="skip", showlegend=False)) - - -def build_price_chart(df, ind, symbol, bar): - """Genel bakış: 3D mum grafiği + EMA/Bollinger çizgileri + arka kenarda 3D hacim sütunları.""" - n = len(df) - v0 = max(0, n - 150) - hi, lo = float(df["h"].iloc[v0:].max()), float(df["l"].iloc[v0:].min()) - pad = (hi - lo) * 0.05 or hi * 0.01 - ylo, yhi = lo - pad, hi + pad - fig = go.Figure() - _candles3d(fig, df, v0, n, symbol) - xs = list(range(v0, n)) - for key, nm, col, w, dash in (("ema20", "EMA20", "#5b8cff", 3, "solid"), ("ema50", "EMA50", "#f2b84b", 3, "solid"), - ("bb_upper", "BB Üst", "rgba(150,160,255,0.6)", 2, "dot"), - ("bb_lower", "BB Alt", "rgba(150,160,255,0.6)", 2, "dot")): - ys = ind[key].iloc[v0:].astype(float).tolist() - fig.add_trace(go.Scatter3d(x=xs, y=ys, z=[0.0] * len(xs), mode="lines", name=nm, - line=dict(color=col, width=w, dash=dash), hoverinfo="skip")) - vol = df["vol"].to_numpy(dtype=float)[v0:n] - vmax = max(float(vol.max()), 1e-12) - up = (df["c"].to_numpy()[v0:n] >= df["o"].to_numpy()[v0:n]) - for flag, col, nm in ((True, "#3ddc97", "Hacim ▲"), (False, "#ff5c72", "Hacim ▼")): - xv, yv, zv = [], [], [] - for k in np.where(up == flag)[0]: - xv += [float(v0 + k), float(v0 + k), None] - yv += [ylo, ylo, None] - zv += [0.0, float(vol[k] / vmax * 0.7), None] - if xv: - fig.add_trace(go.Scatter3d(x=xv, y=yv, z=zv, mode="lines", name=nm, line=dict(color=col, width=5), - hoverinfo="skip")) - _scene3d(fig, df, v0, n, ylo, yhi, 0.8, "Hacim", aspect=(2.8, 1.5, 0.7)) - return _base_layout(fig, f"{symbol} · {bar} · 3D Fiyat & Hacim", 560) - - -def build_signal_price_chart(df, ind, symbol, bar, res): - """3D SMC sinyal haritası. x = zaman, y = fiyat, z = hacim yoğunluğu. - Zemin: mumlar + BOS/CHoCH çizgileri + seviyeler. Yükselen katmanlar: hacim ısı dağı, order block / FVG kutuları. - Fare ile döndür / yakınlaştır.""" - n = len(df) - v0 = max(0, n - 150) - x_end = n - 1 + 10 - hi, lo = float(df["h"].iloc[v0:].max()), float(df["l"].iloc[v0:].min()) - plan = res["plan"] - if plan: - ext = [plan["stop_loss"]] + [px for px, _ in plan["targets"]] - hi, lo = max(hi, max(ext)), min(lo, min(ext)) - pad = (hi - lo) * 0.05 or hi * 0.01 - ylo, yhi = lo - pad, hi + pad - inr = lambda y: y is not None and y == y and ylo <= y <= yhi - fig = go.Figure() - - try: - _volume_surface3d(fig, df, v0, n) - except Exception: - pass - _candles3d(fig, df, v0, n, symbol) - - zones = res.get("smc") or {} - A_ = ind["_arr"] - # Premium / Discount zemin bölgeleri + equilibrium - sh_v, sl_v = float(A_["smc_sh"][-1]), float(A_["smc_sl"][-1]) - if sh_v == sh_v and sl_v == sl_v and sh_v > sl_v: - eq = (sh_v + sl_v) / 2 - x_pd = max(v0, n - 120) - top_, bot_ = min(sh_v, yhi), max(sl_v, ylo) - if top_ > eq: - _rect3d(fig, x_pd, x_end, max(eq, ylo), top_, "#ff5c72", 0.10) - if eq > bot_: - _rect3d(fig, x_pd, x_end, bot_, min(eq, yhi), "#3ddc97", 0.10) - if inr(eq): - _hline3d(fig, x_pd, x_end, eq, "rgba(230,235,242,0.6)", 2, "dash", f"EQ {fmt_price(eq)}") - - # Order Block kutuları (zeminden yükselir) - for key, col, nm in (("bull_ob", "#3ddc97", "OB ▲"), ("bear_ob", "#ff5c72", "OB ▼")): - z_ = zones.get(key) - if z_: - x0_ = max(v0, z_[3]) - _box3d(fig, x0_, x_end, z_[0], z_[1], 0.0, 0.30, col, 0.45) - _label3d(fig, x_end, (z_[0] + z_[1]) / 2, 0.15, nm, col) - # Fair Value Gap kutuları (daha alçak) - for key, col, nm in (("bull_fvg", "#3ddc97", "FVG ▲"), ("bear_fvg", "#ff5c72", "FVG ▼")): - for z_ in zones.get(key, [])[-3:]: - x0_ = max(v0, z_[2] - 2) - _box3d(fig, x0_, x_end, z_[0], z_[1], 0.0, 0.14, col, 0.28) - _label3d(fig, x_end, (z_[0] + z_[1]) / 2, 0.07, nm, col, 8) - # BOS / CHoCH çizgileri - for e in zones.get("events", []): - if e["i"] < v0 or not inr(e["level"]): - continue - col = "#3ddc97" if e["dir"] > 0 else "#ff5c72" - x0_ = max(v0, e["pivot_i"]) - fig.add_trace(go.Scatter3d( - x=[x0_, (x0_ + e["i"]) / 2, e["i"]], y=[e["level"]] * 3, z=[0.0] * 3, mode="lines+text", - line=dict(color=col, width=4, dash="solid" if e["choch"] else "dot"), - text=["", "CHoCH" if e["choch"] else "BOS", ""], textposition="top center", - textfont=dict(size=9, color=col), showlegend=False, hoverinfo="skip")) - # Likidite avı işaretleri - for d, col, nm in ((1, "#3ddc97", "Likidite Avı ▲"), (-1, "#ff5c72", "Likidite Avı ▼")): - pts = [sw for sw in zones.get("sweeps", []) if sw["dir"] == d and sw["i"] >= v0] - if pts: - fig.add_trace(go.Scatter3d( - x=[float(sw["i"]) for sw in pts], - y=[float(df["l"].iloc[sw["i"]] if d > 0 else df["h"].iloc[sw["i"]]) for sw in pts], - z=[0.0] * len(pts), mode="markers", name=nm, - marker=dict(symbol="x", size=5, color=col), hoverinfo="skip")) - - # Acheron VWAP + bantlar - xs_ = np.arange(v0, n) - for key, col, w, dash, nm in (("vwap", "#b388ff", 5, "solid", "VWAP"), - ("vwap_up", "rgba(179,136,255,0.55)", 2, "dot", "VWAP +1σ"), - ("vwap_dn", "rgba(179,136,255,0.55)", 2, "dot", "VWAP −1σ")): - ys_ = A_[key][v0:n].astype(float).copy() - ys_[(ys_ < ylo) | (ys_ > yhi)] = np.nan - if np.isfinite(ys_).any(): - fig.add_trace(go.Scatter3d(x=xs_.astype(float), y=ys_, z=np.zeros(len(xs_)), mode="lines", name=nm, - line=dict(color=col, width=w, dash=dash), hoverinfo="skip", - showlegend=(key == "vwap"))) - # Hacim profili seviyeleri - vp = res.get("vp") - if vp and vp["poc"] == vp["poc"]: - for lab, y, col, w, dash in (("POC", vp["poc"], "#f2b84b", 4, "solid"), ("VAH", vp["vah"], "rgba(242,184,75,0.7)", 2, "dot"), - ("VAL", vp["val"], "rgba(242,184,75,0.7)", 2, "dot")): - if inr(y): - _hline3d(fig, v0, x_end, y, col, w, dash, f"{lab} {fmt_price(y)}") - # Emir defteri duvarları - for key, col, nm in (("support", "#3ddc97", "Alış Duvarı"), ("resist", "#ff5c72", "Satış Duvarı")): - w_ = (res.get("walls") or {}).get(key) - if w_ and inr(w_[0]): - _hline3d(fig, n - 30, x_end, w_[0], col, 6, "solid", f"{nm} {fmt_price(w_[0])} ×{w_[1]:.1f}") - # Destek / direnç - for lv, tc in res["supports"][:3]: - if inr(lv): - _hline3d(fig, v0, x_end, lv, "rgba(61,220,151,0.6)", 2, "dash", f"D {fmt_price(lv)} ×{tc}") - for lv, tc in res["resists"][:3]: - if inr(lv): - _hline3d(fig, v0, x_end, lv, "rgba(255,92,114,0.6)", 2, "dash", f"R {fmt_price(lv)} ×{tc}") - - # Geçmiş sinyal işlemleri - bt = res["backtest"] - if bt and bt["trades"]: - atr_a = ind["_arr"]["atr"] - for d, col, nm in ((1, "#3ddc97", "Geçmiş LONG"), (-1, "#ff5c72", "Geçmiş SHORT")): - tr = [t for t in bt["trades"] if t["dir"] == d and t["entry_i"] >= v0] - if not tr: - continue - ys = [float((df["l"].iloc[t["entry_i"]] - 0.6 * atr_a[t["entry_i"]]) if d > 0 - else (df["h"].iloc[t["entry_i"]] + 0.6 * atr_a[t["entry_i"]])) for t in tr] - hov = [f"{nm} · net teyit {t['score']:+.0f}
Sonuç: {t['why']} ({t['pnl'] * 100:+.2f}% · {t['r']:+.2f}R)" for t in tr] - fig.add_trace(go.Scatter3d( - x=[float(t["entry_i"]) for t in tr], y=ys, z=[0.0] * len(tr), mode="markers", name=nm, text=hov, - hoverinfo="text", marker=dict(symbol="diamond", size=5, color=col))) - - # Güncel işlem planı - if plan: - x0 = max(v0, n - 30) - lines = [("GİRİŞ", plan["entry"], "#e6ebf2"), ("SL", plan["stop_loss"], "#ff5c72")] - lines += [(f"TP{k + 1}", px, "#3ddc97") for k, (px, _) in enumerate(plan["targets"])] - for lab, y, col in lines: - _hline3d(fig, x0, x_end, y, col, 5, "dash", f"{lab} {fmt_price(y)}") - - _scene3d(fig, df, v0, n, ylo, yhi, 1.05, "Hacim yoğunluğu / SMC katmanı") - return _base_layout(fig, f"{symbol} · {bar} · 3D SMC Sinyal Haritası", 680) - - -def build_factor_chart(contribs, symbol): - items = [(l, v) for l, v in contribs if abs(v) > 1e-9] - if not items: - return _empty_fig("Aktif SMC teyidi yok", f"{symbol} · SMC Teyitleri") - items.sort(key=lambda x: x[1]) - labels, vals = [l for l, _ in items], [v for _, v in items] - fig = go.Figure(go.Bar( - x=vals, y=labels, orientation="h", marker_color=["#3ddc97" if v > 0 else "#ff5c72" for v in vals], - text=[f"{v:+.0f}" for v in vals], textposition="outside", cliponaxis=False)) - fig.update_layout(showlegend=False, xaxis_title="SMC teyidi (her biri eşit: ±1 — ağırlık yok)") - return _base_layout(fig, f"{symbol} · SMC Teyitleri (sinyali ne yönlendiriyor?)", max(300, 25 * len(items) + 100)) - - -def build_backtest_chart(bt, df, symbol): - if not bt or not bt.get("stats"): - return _empty_fig("Backtest için yeterli işlem oluşmadı (en az 3 işlem gerekir)", f"{symbol} · Sinyal Backtesti") - start = bt["start"] - ts = df["ts"].iloc[start:] - fig = make_subplots(rows=2, cols=1, row_heights=[0.62, 0.38], vertical_spacing=0.14, - subplot_titles=("Sinyal Stratejisi vs Al&Tut (komisyon dahil, tam pozisyon)", "İşlem Bazında R Çarpanı")) - fig.add_trace(go.Scatter(x=ts, y=bt["equity"], name="Sinyal Stratejisi", line=dict(color="#5b8cff", width=2, shape="hv")), - row=1, col=1) - fig.add_trace(go.Scatter(x=ts, y=bt["bh"], name="Al ve Tut", line=dict(color="#7c8797", width=1.2)), row=1, col=1) - rs = [t["r"] for t in bt["trades"]] - fig.add_trace(go.Bar(x=list(range(1, len(rs) + 1)), y=rs, name="R", - marker_color=["#3ddc97" if r > 0 else "#ff5c72" for r in rs], - text=[t["why"] for t in bt["trades"]], hoverinfo="text+y"), row=2, col=1) - fig.update_xaxes(title_text="İşlem #", row=2, col=1) - return _base_layout(fig, f"{symbol} · Walk-Forward İşlem Simülasyonu", 470) - - -def build_signal_chart(df, ind, symbol): - fig = make_subplots(rows=4, cols=1, shared_xaxes=True, row_heights=[0.27, 0.25, 0.24, 0.24], - vertical_spacing=0.05, - subplot_titles=("RSI (14)", "MACD (12,26,9)", "Stokastik (14,3)", "ADX / DI (14)")) - fig.add_trace(go.Scatter(x=df["ts"], y=ind["rsi"], name="RSI", line=dict(color="#5b8cff")), row=1, col=1) - fig.add_hline(y=70, line=dict(color="#ff5c72", dash="dot", width=1), row=1, col=1) - fig.add_hline(y=30, line=dict(color="#3ddc97", dash="dot", width=1), row=1, col=1) - - hist_colors = np.where(ind["macd_hist"] >= 0, "#3ddc97", "#ff5c72") - fig.add_trace(go.Bar(x=df["ts"], y=ind["macd_hist"], name="Histogram", marker_color=hist_colors), row=2, col=1) - fig.add_trace(go.Scatter(x=df["ts"], y=ind["macd_line"], name="MACD", - line=dict(color="#f2b84b", width=1)), row=2, col=1) - fig.add_trace(go.Scatter(x=df["ts"], y=ind["macd_signal"], name="Sinyal", - line=dict(color="#5b8cff", width=1)), row=2, col=1) - - fig.add_trace(go.Scatter(x=df["ts"], y=ind["stoch_k"], name="%K", line=dict(color="#5b8cff")), row=3, col=1) - fig.add_trace(go.Scatter(x=df["ts"], y=ind["stoch_d"], name="%D", line=dict(color="#f2b84b")), row=3, col=1) - fig.add_hline(y=80, line=dict(color="#ff5c72", dash="dot", width=1), row=3, col=1) - fig.add_hline(y=20, line=dict(color="#3ddc97", dash="dot", width=1), row=3, col=1) - - fig.add_trace(go.Scatter(x=df["ts"], y=ind["adx"], name="ADX", line=dict(color="#e6ebf2", width=1.5)), row=4, col=1) - fig.add_trace(go.Scatter(x=df["ts"], y=ind["plus_di"], name="+DI", line=dict(color="#3ddc97", width=1)), row=4, col=1) - fig.add_trace(go.Scatter(x=df["ts"], y=ind["minus_di"], name="-DI", line=dict(color="#ff5c72", width=1)), row=4, col=1) - fig.add_hline(y=25, line=dict(color="#f2b84b", dash="dot", width=1), row=4, col=1) - - return _base_layout(fig, f"{symbol} · Momentum Göstergeleri (bilgi amaçlı — sinyale dahil değil)", 720) - - -def build_equity_chart(df, symbol): - ret = df["c"].pct_change().fillna(0) - cum = (1 + ret).cumprod() - peak = cum.cummax() - dd = (cum - peak) / peak - fig = make_subplots(rows=2, cols=1, shared_xaxes=True, row_heights=[0.65, 0.35], vertical_spacing=0.06, - subplot_titles=("Kümülatif Getiri (Al ve Tut)", "Drawdown")) - fig.add_trace(go.Scatter(x=df["ts"], y=cum, name="Kümülatif Getiri", line=dict(color="#5b8cff"), - fill="tozeroy", fillcolor="rgba(91,140,255,0.08)"), row=1, col=1) - fig.add_trace(go.Scatter(x=df["ts"], y=dd, name="Drawdown", line=dict(color="#ff5c72"), - fill="tozeroy", fillcolor="rgba(255,92,114,0.12)"), row=2, col=1) - fig.update_layout(showlegend=False) - return _base_layout(fig, f"{symbol} · Risk Profili", 440) - - -def build_depth_chart(bids, asks, symbol): - bids_sorted = sorted(bids, key=lambda x: -x[0]) - asks_sorted = sorted(asks, key=lambda x: x[0]) - bid_prices = [p for p, _ in bids_sorted] - bid_cum = np.cumsum([s for _, s in bids_sorted]) - ask_prices = [p for p, _ in asks_sorted] - ask_cum = np.cumsum([s for _, s in asks_sorted]) - fig = go.Figure() - fig.add_trace(go.Scatter(x=bid_prices, y=bid_cum, name="Alış (Bid)", line=dict(color="#3ddc97", width=2), - fill="tozeroy", fillcolor="rgba(61,220,151,0.15)")) - fig.add_trace(go.Scatter(x=ask_prices, y=ask_cum, name="Satış (Ask)", line=dict(color="#ff5c72", width=2), - fill="tozeroy", fillcolor="rgba(255,92,114,0.15)")) - fig.update_layout(xaxis_title="Fiyat", yaxis_title="Kümülatif Miktar") - return _base_layout(fig, f"{symbol} · Emir Defteri Derinliği", 380) - - -# --------------------------------------------------------------------------- -# TAB CALLBACKS -# --------------------------------------------------------------------------- -def load_overview(symbol, bar): - try: - df = fetch_candles(symbol, bar, 300) - ticker = fetch_ticker(symbol) - ind = compute_indicators(df) - last = float(ticker.get("last") or df["c"].iloc[-1]) - open24h = float(ticker.get("open24h") or df["c"].iloc[0]) - high24h = float(ticker.get("high24h") or df["h"].max()) - low24h = float(ticker.get("low24h") or df["l"].min()) - vol24h = float(ticker.get("volCcy24h") or df["vol"].sum()) - chg = (last - open24h) / open24h if open24h else 0.0 - tone = "up" if chg >= 0 else "down" - cards = [ - metric_card("SON FİYAT", f"${fmt_price(last)}", "", tone), - metric_card("24S DEĞİŞİM", fmt_pct(chg), "", tone), - metric_card("24S YÜKSEK", f"${fmt_price(high24h)}"), - metric_card("24S DÜŞÜK", f"${fmt_price(low24h)}"), - metric_card("24S HACİM (USDT)", f"${fmt_large(vol24h)}"), - ] - return cards_row(cards), build_price_chart(df, ind, symbol, bar) - except Exception as e: - return error_box(f"Veri alınamadı: {e}"), go.Figure() - - -def _reasons_html(reasons): - icon = {"up": "▲", "down": "▼", "flat": "•"} - return "".join(f'
  • {icon[t]}{txt}
  • ' for t, txt in reasons) - - -def load_signal(symbol, bar, closed_only=False, acct=10000, risk_pct=1.0): - empty5 = lambda html: (html, go.Figure(), go.Figure(), go.Figure(), go.Figure()) - try: - df = fetch_candles(symbol, bar, 300) - if closed_only: - df = df[df["confirm"] == 1].reset_index(drop=True) - if len(df) < 30: - return empty5(error_box("Sinyal için yeterli mum verisi yok (en az 30 bar gerekir).")) - acct = float(acct or 0) - risk_pct = float(risk_pct or 0) - res = generate_signal(df, symbol=symbol, bar=bar, mtf=True, micro=True, closed_only=closed_only, - acct=acct, risk_pct=risk_pct) - ind, plan, bt = res["indicators"], res["plan"], res["backtest"] - - # ---- Ana panel ---- - regime_col = {"YÜKSELİŞ": "#3ddc97", "DÜŞÜŞ": "#ff5c72", "BELİRSİZ": "#7c8797"}[res["regime"]] - panel = ( - f'
    ' - f'
    {res["signal"]}
    ' - f'
    KNN + SMC SKORU
    ' - f'
    {res["score"]:+.0f} / ±10
    ' - f'
    GÜVEN
    ' - f'
    %{res["confidence"]}
    ' - f'
    KALİTE NOTU
    ' - f'
    {res["grade"]}
    ' - f'
    PİYASA YAPISI
    ' - f'
    {res["regime"]}
    ' - f'
    ' - ) - tot = res["bull_w"] + res["bear_w"] - bull_pct = (res["bull_w"] / tot * 100) if tot > 0 else 50.0 - consensus = ( - f'
    SMC TEYİT DAĞILIMI · BOĞA %{bull_pct:.0f} / AYI %{100 - bull_pct:.0f}
    ' - f'
    ' - f'
    ' - ) - conf_tags = "".join( - f'
    {k} %{v * 100:.0f}
    ' for k, v in res["conf_parts"].items()) - tag_html = "" - if res["tags"]: - tag_html = '
    ' + "".join( - f'
    {t}
    ' for t, c in res["tags"]) + "
    " - conf_html = f'
    {conf_tags}
    ' - - # ---- İşlem planı + pozisyon boyutu ---- - plan_cards, size_cards = [], [] - if plan: - up = plan["direction"] == "LONG" - plan_cards = [ - metric_card("ÖNERİLEN YÖN", plan["direction"], "", "up" if up else "down"), - metric_card("GİRİŞ", f"${fmt_price(plan['entry'])}"), - metric_card("STOP-LOSS", f"${fmt_price(plan['stop_loss'])}", - f"{plan['stop_type']} · %{plan['risk_frac'] * 100:.2f} risk", "down"), - ] - for k, (px, m) in enumerate(plan["targets"]): - plan_cards.append(metric_card(f"HEDEF {k + 1}", f"${fmt_price(px)}", f"{m:.2f}R", "up")) - sz = plan["sizing"] - eng = res.get("engine") - if sz: - size_cards = [ - metric_card("RİSK TUTARI", f"${fmt_large(sz['risk_amount'])}", - (f"Prophet sonrası risk %{eng['risk_pct']:.2f} (kullanıcı %{risk_pct:g})" if eng else f"Hesabın %{risk_pct:g}'i")), - metric_card("POZİSYON (USDT)", f"${fmt_large(sz['notional'])}", f"Miktar: {sz['qty']:.6g}"), - metric_card("KALDIRAÇ ETKİSİ", f"{sz['leverage']:.2f}x", - "Spot ile yetmez — kaldıraç gerekir" if sz["leverage"] > 1.0 else "Spot ile uygulanabilir", - "down" if sz["leverage"] > 1.0 else "up"), - ] - eng = res.get("engine") if plan else None - if eng: - pp_ = eng.get("prophet") - size_cards.append(metric_card( - "PROPHET RİSK", ("VETO · " + eng["veto"]) if eng["veto"] else f"Risk %{eng['risk_pct']:.2f}", - f"Kullanıcı riski %{eng['user_cap']:g}" - + (f" × Prophet ×{pp_['mult']:.2f}" if pp_ and not pp_.get("error") else " · Prophet verisi yok, çarpan 1"), - "down" if eng["veto"] else "up")) - pro_cards = [] - if PROPHET_ENABLED: - pl, pbt = res.get("prophet_live"), (bt.get("prophet") if bt else None) - live_txt = ("Canlı: bu görünümde çalışmadı" if pl is None else ("Canlı: " + pl["error"] if pl.get("error") else "Canlı: tahmin üretildi")) - if pbt is None: - bt_txt = "Backtest: çalıştırılmadı" - elif pbt.get("reason"): - bt_txt = "Backtest: " + pbt["reason"] - else: - bt_txt = f"Backtest: {pbt['covered']}/{pbt['n']} işlem kapsandı · {pbt['fits']} eğitim (her {pbt['step']} barda)" - ok_ = bool(pl and not pl.get("error")) and bool(pbt and pbt.get("covered")) - pro_cards.append(metric_card("PROPHET DURUMU", _prophet_name(), live_txt + " · " + bt_txt, "up" if ok_ else "down")) - if pl and not pl.get("error"): - pe_ = eng.get("prophet") if eng else None - pfav = pe_["p_fav"] if pe_ else None - pro_cards.append(metric_card( - f"PROPHET TAHMİNİ ({pl['horizon']} bar)", f"{pl['mu_h'] * 100:+.2f}%", - f"Hedef ${fmt_price(pl['price_fc'])} · %{PROPHET_INTERVAL * 100:.0f} bant ${fmt_price(pl['lo'])} – ${fmt_price(pl['hi'])}" - + (f" · P(lehte) %{pfav * 100:.0f}" if pfav is not None else " · sinyal nötr: yön yok"), - ("up" if pfav >= PROPHET_PFAV_FULL else "down") if pfav is not None else ("up" if pl["mu_h"] > 0 else "down"))) - if pl.get("vol_ratio"): - pro_cards.append(metric_card( - "PROPHET VOLATİLİTE", f"×{pl['vol_ratio']:.2f}", - f"ATR% {pl['vol_now'] * 100:.2f} → {pl['vol_fc'] * 100:.2f}" - + (f" · volatilite çarpanı ×{pe_['vol_scale']:.2f}" if pe_ else ""), - "down" if pl["vol_ratio"] > 1.1 else "up")) - stats = bt["stats"] if bt else None - - # ---- Backtest kartları ---- - bt_cards = [] - if stats: - L, S = stats["long"], stats["short"] - wr = lambda x: f"%{x['win_rate'] * 100:.0f}" if x["win_rate"] is not None else "—" - bt_cards = [ - metric_card("İŞLEM SAYISI", f"{stats['n']}", f"Long {L['n']} ({wr(L)}) · Short {S['n']} ({wr(S)})"), - metric_card("KAZANMA ORANI", f"%{stats['win_rate'] * 100:.1f}", f"Maks. üst üste kayıp: {stats['max_consec_loss']}", - "up" if stats["win_rate"] >= 0.45 else "down"), - metric_card("PROFIT FACTOR", f"{stats['profit_factor']:.2f}", "Brüt kazanç / brüt kayıp", - "up" if stats["profit_factor"] >= 1.2 else "down"), - metric_card("BEKLENTİ / İŞLEM", fmt_pct(stats["expectancy"]), f"Ort. {stats['avg_r']:+.2f}R (komisyon dahil)", - "up" if stats["expectancy"] > 0 else "down"), - metric_card("SQN", f"{stats['sqn']:.2f}", "Sistem kalitesi (>2 iyi, >3 çok iyi)", - "up" if stats["sqn"] >= 2 else "neutral"), - metric_card("STRATEJİ MAKS. DD", fmt_pct(stats["max_dd"]), "", "down"), - metric_card("STRATEJİ vs AL&TUT", f"{fmt_pct(stats['total_return'])} / {fmt_pct(stats['bh_return'])}", - "Aynı dönem, komisyon dahil", "up" if stats["total_return"] > stats["bh_return"] else "down"), - ] - bsz = bt.get("sized") - if bsz: - bt_cards.append(metric_card( - "BOYUTLANDIRMA (PROPHET)", f"{fmt_pct(bsz['total_return'])} · DD {fmt_pct(bsz['max_dd'])}", - f"Sabit %{SIZE_RISK_BASE * 100:.0f} risk (Prophet'siz): {fmt_pct(bsz['flat_return'])} · DD {fmt_pct(bsz['flat_max_dd'])} · " - f"{bsz['n_active']}/{bsz['n']} işlem açık", "up" if bsz["total_return"] > 0 else "down")) - pdg = bsz.get("prophet") - if pdg: - rv = f"{pdg['r_veto']:+.2f}R" if pdg["r_veto"] is not None else "—" - rk = f"{pdg['r_kept']:+.2f}R" if pdg["r_kept"] is not None else "—" - cr = f" · korelasyon(P,R) {pdg['corr']:+.2f}" if pdg.get("corr") is not None else "" - bt_cards.append(metric_card( - "PROPHET ETKİSİ (BACKTEST)", f"{fmt_pct(bsz['total_return'])} · DD {fmt_pct(bsz['max_dd'])}", - f"Prophet'siz: {fmt_pct(bsz['flat_return'])} · DD {fmt_pct(bsz['flat_max_dd'])} · {pdg['n']} işlem kapsandı, " - f"{pdg['n_veto']} veto (ort. R: veto {rv} / kalan {rk}){cr}", - "up" if bsz["total_return"] >= bsz["flat_return"] else "down")) - elif PROPHET_ENABLED: - bt_cards.append(metric_card("PROPHET (BACKTEST)", "Uygulanmadı", - (bt.get("prophet") or {}).get("reason") or "Kapsanan işlem yok", "down")) - acc_cards = [] - if bt and bt["acc"]: - a = bt["acc"] - acc_cards.append(metric_card("YÖN DOĞRULUĞU", f"%{a['accuracy'] * 100:.1f}", - f"{a['n_signals']} bağımsız sinyal · {a['forward_bars']} bar ileri · net getiri {a['net_ret'] * 100:+.2f}%/sinyal (komisyon sonrası)", - "up" if a["accuracy"] >= 0.5 else "down")) - for key_, lab_ in (("long_side", "DOĞRULUK · LONG"), ("short_side", "DOĞRULUK · SHORT")): - t = a.get(key_) - if t: - acc_cards.append(metric_card(lab_, f"%{t['accuracy'] * 100:.1f}", f"{t['n_signals']} sinyal", - "up" if t["accuracy"] >= 0.5 else "down")) - - # ---- Piyasa bağlamı ---- - ctx = [] - if res.get("xgb_p") is not None: - xp_ = res["xgb_p"] - xv_ = "▲ +1 oy" if xp_ >= XGB_P_VOTE else ("▼ −1 oy" if xp_ <= 1.0 - XGB_P_VOTE else "oy yok") - ctx.append(metric_card(f"XGBOOST ({KNN_FORWARD} BAR)", f"%{xp_ * 100:.0f}", f"P(yükseliş) · {_xgb_name()} · teyit: {xv_}", - "up" if xp_ >= XGB_P_VOTE else ("down" if xp_ <= 1.0 - XGB_P_VOTE else "neutral"))) - _tt = lambda t: "Yükseliş" if t > 0 else ("Düşüş" if t < 0 else "Belirsiz") - if res.get("pd") is not None: - pdv = res["pd"] - ctx.append(metric_card("KONUM (PREMIUM / DISCOUNT)", f"%{pdv * 100:.0f}", - "Discount (<%45) · Premium (>%55) · swing aralığı", - "up" if pdv <= 0.45 else ("down" if pdv >= 0.55 else "neutral"))) - for m in res["mtf"]: - if m["aligned"] is None: - ctx.append(metric_card(f"{m['bar']} TEYİDİ", "Nötr", f"Üst zaman dilimi yapısı: {_tt(m['trend'])}")) - else: - ctx.append(metric_card(f"{m['bar']} TEYİDİ", "Uyumlu ✓" if m["aligned"] else "Ters Yönlü ⚠", - f"Üst zaman dilimi yapısı: {_tt(m['trend'])}", "up" if m["aligned"] else "down")) - if res["funding"] is not None: - f_ = res["funding"] - ctx.append(metric_card("FUNDING (8S)", f"{f_ * 100:+.4f}%", "Pozitif = long kalabalık", - "down" if f_ > 0.0002 else ("up" if f_ < -0.0002 else "neutral"))) - if res["imbalance"] is not None: - im = res["imbalance"] - ctx.append(metric_card("DEFTER DENGESİ", f"{im * 100:+.0f}%", "Derinlik ağırlıklı", - "up" if im > 0.2 else ("down" if im < -0.2 else "neutral"))) - if res["supports"]: - lv, tc = res["supports"][0] - ctx.append(metric_card("YAKIN DESTEK", f"${fmt_price(lv)}", f"{tc} temas · {(res['price'] - lv) / res['atr']:.1f} ATR", "up")) - if res["resists"]: - lv, tc = res["resists"][0] - ctx.append(metric_card("YAKIN DİRENÇ", f"${fmt_price(lv)}", f"{tc} temas · {(lv - res['price']) / res['atr']:.1f} ATR", "down")) - ctx.append(metric_card("VOLATİLİTE YÜZDELİĞİ", f"%{res['atr_rank'] * 100:.0f}", "ATR/fiyat, son 100 bar", - "down" if res["atr_rank"] > 0.9 else "neutral")) - - reasons_box = (f'
    SMC ANALİZİ
    ' - f'
    ') - bt_note = "" - if stats: - bt_note = ('
    Backtest: son ~220 barda sinyaller üst üste binmeden işleme çevrilir; giriş bir sonraki barın açılışı, ' - f'çıkış {"KNN dinamik SL/TP (komşu sapmaları; yetersizse " + str(SL_ATR) + "/" + str(TP_ATR) + "×ATR)" if DYNAMIC_EXITS else "SL " + str(SL_ATR) + "×ATR / TP " + str(TP_ATR) + "×ATR"} / {MAX_HOLD_BARS} bar; komisyon %{FEE_PER_SIDE * 100:.1f}/yön dahildir. ' - 'Emir defteri, funding, S/R ve MTF katmanları canlıdır, geçmişe uygulanamaz.
    ') - - html = (panel + consensus + conf_html + tag_html - + (section("İŞLEM PLANI") + cards_row(plan_cards) if plan_cards else "") - + (section("PROPHET RİSK KATMANI") + cards_row(pro_cards) if pro_cards else "") - + (section("POZİSYON BOYUTU") + cards_row(size_cards) if size_cards else "") - + (section("GEÇMİŞ PERFORMANS (WALK-FORWARD)") + cards_row(bt_cards) + cards_row(acc_cards) + bt_note - if (bt_cards or acc_cards) else "") - + section("PİYASA BAĞLAMI") + cards_row(ctx) + reasons_box) - return (html, build_signal_price_chart(df, ind, symbol, bar, res), build_factor_chart(res["contribs"], symbol), - build_backtest_chart(bt, df, symbol), build_signal_chart(df, ind, symbol)) - except Exception as e: - return empty5(error_box(f"Veri alınamadı: {e}")) - - -def load_risk(symbol, bar): - try: - df = fetch_candles(symbol, bar, 300) - rm = risk_metrics(df, bar) - if rm is None: - return error_box("Risk hesaplaması için yeterli veri yok."), go.Figure() - cards = [ - metric_card("YILLIKLANDIRILMIŞ VOLATİLİTE", fmt_pct(rm["volatility_annual"])), - metric_card("YILLIKLANDIRILMIŞ GETİRİ", fmt_pct(rm["return_annual"]), "", - "up" if rm["return_annual"] >= 0 else "down"), - metric_card("SHARPE ORANI", f"{rm['sharpe']:.2f}", "", "up" if rm["sharpe"] > 0 else "down"), - metric_card("MAKS. DRAWDOWN", fmt_pct(rm["max_drawdown"]), "", "down"), - metric_card("GÜNCEL DRAWDOWN", fmt_pct(rm["current_dd"]), "", - "down" if rm["current_dd"] < 0 else "neutral"), - metric_card("VaR %95 (BAR)", fmt_pct(rm["var95_bar"]), "", "down"), - metric_card("CVaR %95 (BAR)", fmt_pct(rm["cvar95_bar"]), "", "down"), - ] - return cards_row(cards), build_equity_chart(df, symbol) - except Exception as e: - return error_box(f"Veri alınamadı: {e}"), go.Figure() - - -def load_orderbook(symbol, depth): - try: - ob = fetch_orderbook(symbol, int(depth)) - bids = [(float(p), float(s)) for p, s, *_ in ob.get("bids", [])] - asks = [(float(p), float(s)) for p, s, *_ in ob.get("asks", [])] - if not bids or not asks: - return error_box("Emir defteri verisi alınamadı."), go.Figure() - best_bid, best_ask = bids[0][0], asks[0][0] - spread = best_ask - best_bid - spread_pct = spread / best_ask if best_ask else 0.0 - bid_vol = sum(s for _, s in bids) - ask_vol = sum(s for _, s in asks) - imbalance = (bid_vol - ask_vol) / (bid_vol + ask_vol) if (bid_vol + ask_vol) else 0.0 - tone = "up" if imbalance > 0.05 else ("down" if imbalance < -0.05 else "neutral") - cards = [ - metric_card("EN İYİ ALIŞ", f"${fmt_price(best_bid)}", "", "up"), - metric_card("EN İYİ SATIŞ", f"${fmt_price(best_ask)}", "", "down"), - metric_card("SPREAD", f"${fmt_price(spread)}", fmt_pct(spread_pct)), - metric_card("DEFTER DENGESİZLİĞİ", fmt_pct(imbalance), "Pozitif = alış baskısı", tone), - ] - return cards_row(cards), build_depth_chart(bids, asks, symbol) - except Exception as e: - return error_box(f"Veri alınamadı: {e}"), go.Figure() - - -@_gpu -def refresh_all(symbol, bar, depth, closed_only, acct, risk_pct): - ov_html, ov_fig = load_overview(symbol, bar) - sig = load_signal(symbol, bar, closed_only, acct, risk_pct) - risk_html, risk_fig = load_risk(symbol, bar) - ob_html, ob_fig = load_orderbook(symbol, depth) - return (ov_html, ov_fig, *sig, risk_html, risk_fig, ob_html, ob_fig) - - -def refresh_signal(symbol, bar, closed_only, acct, risk_pct): - """Yalnızca sinyal sekmesini yeniler (risk/kapanmış-mum ayarı değişince hızlı güncelleme).""" - return load_signal(symbol, bar, closed_only, acct, risk_pct) - - -SCAN_MAX = 150 -SCAN_MODES = ["Tümü (skora göre)", "Yalnızca AL yönlü", "Yalnızca SAT yönlü", "Yalnızca yüksek kalite (A / A+)"] - - -def _scan_one(sym, bar, all_tickers, closed_only): - df = fetch_candles(sym, bar, 300) - if closed_only: - df = df[df["confirm"] == 1].reset_index(drop=True) - # MTF ve mikro-yapı katmanları taramada kapalı (API yükünü azaltmak için); tekil analizde açık. - res = generate_signal(df, symbol=sym, bar=bar, mtf=False, micro=False, closed_only=closed_only) - rm = risk_metrics(df, bar) - t = all_tickers.get(sym, {}) - last = float(t.get("last") or df["c"].iloc[-1]) - open24h = float(t.get("open24h") or df["c"].iloc[0]) - chg24h = (last - open24h) / open24h if open24h else 0.0 - pdv = res["pd"] - zone = "—" if pdv is None else ("Discount" if pdv <= 0.45 else ("Premium" if pdv >= 0.55 else "Denge")) - events = res["smc"]["events"] - last_ev = "—" - if events: - e = events[-1] - last_ev = f"{'CHoCH' if e['choch'] else 'BOS'} {'▲' if e['dir'] > 0 else '▼'} ({len(df) - 1 - e['i']} bar önce)" - bt = res["backtest"] - st = bt["stats"] if bt else None - acc = bt["acc"] if bt else None - return { - "Sembol": sym, "Fiyat": last, "24S %": round(chg24h * 100, 2), "Sinyal": res["signal"], - "Skor": res["score"], "Kalite": res["grade"], "Güven %": res["confidence"], "Yapı": res["regime"], - "Bölge": zone, "Son Kırılım": last_ev, - "RSI": round(res["indicators"]["rsi"].iloc[-1], 1), - "Kazanma %": round(st["win_rate"] * 100, 1) if st else None, - "Profit Factor": round(st["profit_factor"], 2) if st else None, - "Beklenti %": round(st["expectancy"] * 100, 2) if st else None, - "Yön Doğruluğu %": round(acc["accuracy"] * 100, 1) if acc else None, - "Volatilite (Yıllık) %": round(rm["volatility_annual"] * 100, 1) if rm else None, - "Sharpe": round(rm["sharpe"], 2) if rm else None, - } - - -def run_scanner(symbols_text, bar, closed_only=False, mode=SCAN_MODES[0], progress=gr.Progress()): - symbols = [s.strip().upper() for s in symbols_text.split(",") if s.strip()][:SCAN_MAX] - if not symbols: - return pd.DataFrame() - try: - all_tickers = {t["instId"]: t for t in fetch_all_tickers("SPOT")} - except Exception: - all_tickers = {} - - rows, done = [], 0 - with ThreadPoolExecutor(max_workers=4) as ex: # ağ beklemesini paralelle (OKX limiti için 4 ile sınırlı) - futs = {ex.submit(_scan_one, s, bar, all_tickers, closed_only): s for s in symbols} - for f in as_completed(futs): - done += 1 - progress(done / len(symbols), desc=f"{futs[f]} tarandı ({done}/{len(symbols)})") - try: - rows.append(f.result()) - except Exception: - continue - if not rows: - return pd.DataFrame() - out = pd.DataFrame(rows) - if mode == SCAN_MODES[1]: - out = out[out["Skor"] >= SIGNAL_THRESHOLD].sort_values("Skor", ascending=False) - elif mode == SCAN_MODES[2]: - out = out[out["Skor"] <= -SIGNAL_THRESHOLD].sort_values("Skor", ascending=True) - elif mode == SCAN_MODES[3]: - out = out[out["Kalite"].isin(["A", "A+"])].sort_values("Güven %", ascending=False) - else: - out = out.sort_values("Skor", ascending=False) - return out.reset_index(drop=True) - - -# --------------------------------------------------------------------------- -# STYLE — dark "trading terminal" identity (deliberate, not default SaaS chrome) -# --------------------------------------------------------------------------- -HEAD = """ - - - - -""" - -CSS = """ -:root { - --bg-void: #0a0e14; - --bg-panel: #12171f; - --line: #232a35; - --text-primary: #e6ebf2; - --text-muted: #7c8797; - --accent-long: #3ddc97; - --accent-short: #ff5c72; - --accent-signal: #f2b84b; - --accent-primary: #5b8cff; -} -.gradio-container { background: var(--bg-void) !important; color: var(--text-primary) !important; - font-family: 'IBM Plex Sans', system-ui, sans-serif !important; } -h1, h2, h3 { font-family: 'Space Grotesk', sans-serif !important; letter-spacing: -0.01em; } - -#masthead { display:flex; justify-content:space-between; align-items:center; flex-wrap:wrap; gap:10px; - border-bottom: 1px solid var(--line); padding: 14px 4px 18px 4px; margin-bottom: 6px; } -#masthead .wordmark { font-family:'Space Grotesk',sans-serif; font-weight:700; font-size:1.5rem; letter-spacing:-0.02em; } -#masthead .wordmark span { color: var(--accent-primary); } -#masthead .tags { display:flex; gap:8px; flex-wrap:wrap; } -#masthead .tag { font-size:0.7rem; padding:3px 9px; border:1px solid var(--line); border-radius:3px; - color:var(--text-muted); font-family:'IBM Plex Mono',monospace; } - -.metric-row { display:flex; flex-wrap:wrap; gap:10px; margin:8px 0 16px 0; } -.metric-card { background: var(--bg-panel); border:1px solid var(--line); border-radius:4px; - padding:12px 16px; min-width:150px; flex:1; } -.mc-label { font-size:0.66rem; color:var(--text-muted); letter-spacing:0.04em; margin-bottom:6px; } -.mc-value { font-size:1.24rem; font-weight:600; color:var(--text-primary); - font-family:'IBM Plex Mono',monospace; } -.mc-sub { font-size:0.72rem; color:var(--text-muted); margin-top:4px; font-family:'IBM Plex Mono',monospace; } -.metric-card.mc-up .mc-value { color: var(--accent-long); } -.metric-card.mc-down .mc-value { color: var(--accent-short); } - -.section-title { font-family:'Space Grotesk',sans-serif; font-size:0.78rem; font-weight:600; letter-spacing:0.08em; - color:var(--text-muted); margin:18px 0 0 0; padding-top:12px; border-top:1px solid var(--line); } - -.signal-panel { display:flex; align-items:center; flex-wrap:wrap; gap:20px; background:var(--bg-panel); - border:1px solid var(--line); border-radius:4px; padding:18px 20px; margin-bottom:12px; } -.signal-badge { font-family:'Space Grotesk',sans-serif; font-weight:700; font-size:1.6rem; color:var(--tone); - border:2px solid var(--tone); border-radius:4px; padding:10px 22px; } -.signal-meta .mc-value { font-size:1.35rem; } - -.consensus { background:var(--bg-panel); border:1px solid var(--line); border-radius:4px; padding:12px 16px; margin-bottom:12px; } -.cbar { display:flex; height:8px; border-radius:2px; overflow:hidden; background:var(--line); } -.cbull { background: var(--accent-long); } -.cbear { background: var(--accent-short); } - -.tags { display:flex; gap:8px; flex-wrap:wrap; } -.tags .tag { font-size:0.72rem; font-weight:600; padding:5px 12px; border:1px solid var(--line); - border-radius:3px; font-family:'IBM Plex Mono',monospace; letter-spacing:0.03em; } - -.reasons-box { background:var(--bg-panel); border:1px solid var(--line); border-radius:4px; padding:16px 20px; margin-top:8px; } -.reasons-box ul { margin:8px 0 0 0; padding-left:0; list-style:none; } -.reasons-box li { margin-bottom:6px; color:var(--text-primary); font-size:0.9rem; } -.reasons-box .ri { display:inline-block; width:18px; font-size:0.7rem; } -.reasons-box .r-up .ri { color: var(--accent-long); } -.reasons-box .r-down .ri { color: var(--accent-short); } -.reasons-box .r-flat .ri { color: var(--text-muted); } - -.note { color:var(--text-muted); font-size:0.74rem; line-height:1.5; margin:-6px 0 8px 0; } - -.error-box { background: rgba(255,92,114,0.08); border:1px solid var(--accent-short); color:var(--accent-short); - padding:14px 18px; border-radius:4px; font-family:'IBM Plex Mono',monospace; font-size:0.85rem; } - -.disclaimer { border-top:1px solid var(--line); margin-top:22px; padding-top:14px; color:var(--text-muted); - font-size:0.76rem; line-height:1.55; } - -/* ===================== 3D ARAYÜZ KATMANI ===================== */ -body, gradio-app { background: var(--bg-void) !important; } -.gradio-container { background: transparent !important; position: relative; z-index: 1; } - -/* perspektifli zemin ızgarası */ -body::before { content:""; position:fixed; left:-60%; right:-60%; bottom:-8%; height:75vh; z-index:0; pointer-events:none; - background-image: linear-gradient(rgba(91,140,255,.20) 1px, transparent 1px), - linear-gradient(90deg, rgba(91,140,255,.20) 1px, transparent 1px); - background-size: 64px 64px; - transform: perspective(520px) rotateX(64deg); transform-origin: 50% 100%; - -webkit-mask-image: linear-gradient(to top, #000 0%, transparent 80%); - mask-image: linear-gradient(to top, #000 0%, transparent 80%); - animation: gridflow 7s linear infinite; } -@keyframes gridflow { to { background-position: 0 64px; } } - -/* masthead: ekstrüde 3D başlık */ -#masthead { perspective: 700px; } -#masthead .wordmark { transform: rotateY(-7deg) rotateX(4deg); transform-origin: left center; transform-style: preserve-3d; - text-shadow: 1px 1px 0 #2b4488, 2px 2px 0 #243a75, 3px 3px 0 #1d2f60, 4px 4px 0 #17254d, 6px 8px 14px rgba(0,0,0,.65); } -#masthead .tag { box-shadow: 0 2px 0 #06090d, 0 6px 10px -4px rgba(0,0,0,.6); background: #10151c; } - -/* paneller / kartlar: derinlik + fare eğimi */ -.metric-card, .signal-panel, .consensus, .reasons-box, .error-box { - --rx: 0deg; --ry: 0deg; --tz: 0px; - transform-style: preserve-3d; - transform: perspective(900px) rotateX(var(--rx)) rotateY(var(--ry)) translateZ(var(--tz)); - transition: transform .22s ease-out, box-shadow .22s ease-out; - will-change: transform; - background: linear-gradient(155deg, #1b222d 0%, #12171f 55%, #0d1219 100%); - border-color: #2b3442; border-top-color: rgba(255,255,255,.14); - box-shadow: 0 1px 0 rgba(255,255,255,.06) inset, - 1px 1px 0 #0c1016, 2px 2px 0 #0b0f14, 3px 3px 0 #0a0d12, 4px 4px 0 #090c10, - 0 20px 32px -12px rgba(0,0,0,.75); } -.metric-card:hover { --tz: 16px; - box-shadow: 0 1px 0 rgba(255,255,255,.08) inset, - 1px 1px 0 #0c1016, 2px 2px 0 #0b0f14, 3px 3px 0 #0a0d12, 4px 4px 0 #090c10, 5px 5px 0 #080b0f, - 0 30px 40px -12px rgba(0,0,0,.85); } -.metric-card .mc-label, .metric-card .mc-sub { transform: translateZ(10px); } -.metric-card .mc-value { transform: translateZ(26px); text-shadow: 0 1px 0 #000, 0 3px 6px rgba(0,0,0,.6); } -.metric-card.mc-up { border-left: 3px solid var(--accent-long); } -.metric-card.mc-down { border-left: 3px solid var(--accent-short); } - -/* sinyal rozeti: kabartma tuş */ -.signal-badge { transform: translateZ(30px); background: rgba(0,0,0,.25); - text-shadow: 0 2px 0 rgba(0,0,0,.6), 0 0 18px var(--tone); - box-shadow: 0 1px 0 rgba(255,255,255,.12) inset, 0 6px 0 rgba(0,0,0,.55), 0 16px 26px -6px var(--tone); } - -/* konsensüs çubuğu: oluk + parlak dolgu */ -.cbar { height: 12px; border-radius: 6px; box-shadow: inset 0 2px 4px rgba(0,0,0,.7), 0 1px 0 rgba(255,255,255,.06); } -.cbull { background: linear-gradient(to bottom, #6af0b8, #2fb87e 55%, #1f8f5f); } -.cbear { background: linear-gradient(to bottom, #ff8a9a, #e64560 55%, #b92e47); } - -.tags .tag { background: #10151c; box-shadow: 0 2px 0 #06090d, 0 6px 10px -4px rgba(0,0,0,.6); } - -/* Gradio blokları (grafikler, girişler) */ -.gradio-container .block, .gradio-container .form { - box-shadow: 0 1px 0 rgba(255,255,255,.05) inset, 0 14px 28px -14px rgba(0,0,0,.8) !important; - border-color: #2b3442 !important; } - -/* 3D tuşlar */ -.gradio-container button.primary, .gradio-container button.lg.primary { - position: relative; transform: translateY(0); transition: transform .08s ease, box-shadow .08s ease, filter .15s; - background: linear-gradient(to bottom, #6f9bff, #4a78f0) !important; border: 0 !important; - box-shadow: 0 1px 0 rgba(255,255,255,.35) inset, 0 6px 0 #2a4a9e, 0 12px 18px -4px rgba(0,0,0,.6) !important; } -.gradio-container button.primary:hover { filter: brightness(1.08); transform: translateY(-2px); - box-shadow: 0 1px 0 rgba(255,255,255,.35) inset, 0 8px 0 #2a4a9e, 0 16px 22px -4px rgba(0,0,0,.6) !important; } -.gradio-container button.primary:active { transform: translateY(5px); - box-shadow: 0 1px 0 rgba(255,255,255,.25) inset, 0 1px 0 #2a4a9e, 0 3px 6px rgba(0,0,0,.6) !important; } - -/* sekmeler: öne çıkan 3D tab */ -.gradio-container button[role="tab"] { transition: transform .15s, box-shadow .15s; border-radius: 6px 6px 0 0 !important; } -.gradio-container button[role="tab"][aria-selected="true"] { - transform: perspective(500px) rotateX(10deg) translateY(-3px); transform-origin: bottom; - background: linear-gradient(to bottom, #1d2530, #12171f) !important; - box-shadow: 0 -6px 14px -6px rgba(91,140,255,.45), 0 1px 0 rgba(255,255,255,.08) inset; } - -@media (prefers-reduced-motion: reduce) { - body::before { animation: none; } - .metric-card, .signal-panel, .consensus, .reasons-box, .error-box { transition: none; transform: none; } -} -@media (max-width: 640px) { body::before { display: none; } #masthead .wordmark { transform: none; } } -""" - -# --------------------------------------------------------------------------- -# TRADINGVIEW GÖMME (ücretsiz Advanced Chart widget — iframe/srcdoc; kendi sinyal/SL/TP çizgilerimizi ÇİZMEZ) -# --------------------------------------------------------------------------- -TV_INTERVAL = {"1m": "1", "3m": "3", "5m": "5", "15m": "15", "30m": "30", "1H": "60", "2H": "120", - "4H": "240", "6H": "360", "12H": "720", "1D": "D", "1W": "W"} - - -def tv_symbol(symbol): - """OKX 'BTC-USDT' -> TradingView 'OKX:BTCUSDT'.""" - return "OKX:" + str(symbol or "BTC-USDT").replace("-", "").upper() - - -def tv_widget_html(symbol, bar, height=680): - cfg = {"autosize": True, "symbol": tv_symbol(symbol), "interval": TV_INTERVAL.get(bar, "60"), "timezone": "Etc/UTC", - "theme": "dark", "style": "1", "locale": "tr", "backgroundColor": CHART_BG, "gridColor": "rgba(255,255,255,0.05)", - "allow_symbol_change": True, "hide_side_toolbar": False, "withdateranges": True, "save_image": True, - "support_host": "https://www.tradingview.com"} - doc = ('' - '
    ' - '
    ') - return (f'') - - -# --------------------------------------------------------------------------- -# GRADIO UI -# --------------------------------------------------------------------------- -if os.environ.get("APP_NO_LAUNCH") != "1": - POPULAR_SYMBOLS = _filter_listed(POPULAR_SYMBOLS) -SCAN_DEFAULT = list(POPULAR_SYMBOLS) # varsayılan tarama listesi: tüm coinler - - -with gr.Blocks(title="OKX Quant Terminal") as demo: - - gr.HTML( - '
    ' - '
    OKX QUANT TERMINAL
    ' - '
    SMART MONEY ENGINE v3 · 3D
    ' - '
    PUBLIC MARKET DATA
    ' - '
    READ-ONLY · EMİR GÖNDERİLMEZ
    ' - '
    ' - ) - - with gr.Row(): - symbol_dd = gr.Dropdown(choices=POPULAR_SYMBOLS, value="BTC-USDT", allow_custom_value=True, - label="Sembol", scale=2) - bar_dd = gr.Dropdown(choices=BAR_OPTIONS, value="1H", label="Zaman Dilimi", scale=1) - depth_slider = gr.Slider(5, 50, value=20, step=5, label="Defter Derinliği", scale=1) - refresh_btn = gr.Button("���� Yenile", variant="primary", scale=1) - auto_cb = gr.Checkbox(label="Otomatik (15sn)", value=False, scale=1) - - with gr.Row(): - closed_cb = gr.Checkbox(label="Sadece kapanmış mumlarla sinyal (repaint yok)", value=False, scale=2) - acct_num = gr.Number(value=10000, label="Hesap Büyüklüğü (USDT)", minimum=0, scale=1) - risk_sl = gr.Slider(0.25, 5, value=1, step=0.25, label="İşlem Başına Risk (%)", scale=1) - - timer = gr.Timer(15, active=False) - - with gr.Tabs(): - with gr.Tab("📊 Genel Bakış"): - ov_html = gr.HTML() - ov_plot = gr.Plot() - with gr.Tab("🎯 Sinyal Motoru"): - sig_html = gr.HTML() - sig_map_plot = gr.Plot() - sig_factor_plot = gr.Plot() - sig_bt_plot = gr.Plot() - sig_ind_plot = gr.Plot() - with gr.Tab("⚠️ Risk Analitiği"): - risk_html = gr.HTML() - risk_plot = gr.Plot() - with gr.Tab("📖 Emir Defteri"): - ob_html = gr.HTML() - ob_plot = gr.Plot() - with gr.Tab("📈 TradingView"): - gr.Markdown("TradingView canlı grafiği (seçili sembol ve zaman dilimi). Widget yalnızca fiyat grafiğidir; terminalin sinyal / SL / TP seviyeleri üzerine çizilmez.") - tv_html = gr.HTML() - with gr.Tab("🔍 Çoklu Varlık Tarayıcı"): - gr.Markdown("Popüler pariteleri SMC teyit skoruna göre sıralar. Kendi listenizi virgülle ayırıp girebilirsiniz (en fazla 150 sembol; çok sembol taramak uzun sürer).") - with gr.Row(): - scan_symbols = gr.Textbox(value=",".join(SCAN_DEFAULT), label=f"Semboller ({len(SCAN_DEFAULT)} coin)", lines=4, scale=3) - scan_bar = gr.Dropdown(choices=BAR_OPTIONS, value="4H", label="Zaman Dilimi", scale=1) - scan_mode = gr.Dropdown(choices=SCAN_MODES, value=SCAN_MODES[0], label="Filtre", scale=2) - scan_btn = gr.Button("🔍 Taramayı Başlat", variant="primary") - scan_out = gr.Dataframe(interactive=False, wrap=True) - - gr.HTML( - '
    Bu panel yalnızca eğitim ve bilgilendirme amaçlıdır; yatırım tavsiyesi ' - 'değildir. Tüm veriler OKX genel (public) piyasa API\'sinden alınır; hiçbir API anahtarı ' - 'kullanılmaz ve hiçbir gerçek emir gönderilmez. Kripto varlıklar yüksek oynaklık içerir; geçmiş ' - 'performans (backtest dahil) gelecekteki sonuçların göstergesi değildir. Backtest sonuçları ' - 'kısa örneklem üzerindedir ve istatistiksel olarak sınırlıdır.
    ' - ) - - io_inputs = [symbol_dd, bar_dd, depth_slider, closed_cb, acct_num, risk_sl] - io_outputs = [ov_html, ov_plot, sig_html, sig_map_plot, sig_factor_plot, sig_bt_plot, sig_ind_plot, - risk_html, risk_plot, ob_html, ob_plot] - sig_inputs = [symbol_dd, bar_dd, closed_cb, acct_num, risk_sl] - sig_outputs = [sig_html, sig_map_plot, sig_factor_plot, sig_bt_plot, sig_ind_plot] - - refresh_btn.click(refresh_all, io_inputs, io_outputs) - timer.tick(refresh_all, io_inputs, io_outputs) - auto_cb.change(lambda active: gr.Timer(active=active), auto_cb, timer) - closed_cb.change(refresh_signal, sig_inputs, sig_outputs) - acct_num.submit(refresh_signal, sig_inputs, sig_outputs) - risk_sl.release(refresh_signal, sig_inputs, sig_outputs) - scan_btn.click(run_scanner, [scan_symbols, scan_bar, closed_cb, scan_mode], scan_out) - demo.load(refresh_all, io_inputs, io_outputs) - # TradingView sekmesi yalnızca sembol / zaman dilimi değişince yenilenir (15 sn'lik otomatik yenilemede iframe sıfırlanmasın) - symbol_dd.change(tv_widget_html, [symbol_dd, bar_dd], tv_html) - bar_dd.change(tv_widget_html, [symbol_dd, bar_dd], tv_html) - demo.load(tv_widget_html, [symbol_dd, bar_dd], tv_html) - -if os.environ.get("APP_NO_LAUNCH") != "1": # knn_calibrate.py arayüzü açmadan içe aktarır - demo.queue().launch( - theme=gr.themes.Base(primary_hue="blue", neutral_hue="slate"), - css=CSS, - head=HEAD, - ssr_mode=False, - )