Spaces:
Sleeping
Sleeping
Update app.py
Browse files
app.py
CHANGED
|
@@ -147,41 +147,43 @@ async def fetch_ohlcv(session, tools, symbol, timeframe, exchange, total):
|
|
| 147 |
return out.to_dict("records")
|
| 148 |
|
| 149 |
|
| 150 |
-
# ----------------------------------------------------------------- 2. MCP:
|
| 151 |
-
#
|
| 152 |
-
#
|
| 153 |
-
#
|
| 154 |
-
#
|
| 155 |
-
|
| 156 |
-
|
| 157 |
-
|
| 158 |
-
|
| 159 |
-
|
| 160 |
-
|
| 161 |
-
|
| 162 |
-
|
| 163 |
-
return
|
| 164 |
-
|
| 165 |
-
|
| 166 |
-
def
|
| 167 |
-
|
| 168 |
-
|
| 169 |
-
|
| 170 |
-
|
| 171 |
-
|
| 172 |
-
|
| 173 |
-
|
| 174 |
-
|
| 175 |
-
return r
|
| 176 |
-
return None
|
| 177 |
|
| 178 |
-
|
|
|
|
|
|
|
|
|
|
| 179 |
if not rows:
|
| 180 |
return None
|
| 181 |
d = pd.DataFrame(rows)
|
| 182 |
-
tcol = next((c for c in ("timestamp", "
|
| 183 |
-
|
| 184 |
-
|
|
|
|
| 185 |
return None
|
| 186 |
ts = d[tcol]
|
| 187 |
if pd.api.types.is_numeric_dtype(ts):
|
|
@@ -190,67 +192,98 @@ def parse_funding(payload):
|
|
| 190 |
ts = pd.to_datetime(ts)
|
| 191 |
if getattr(ts.dt, "tz", None) is not None:
|
| 192 |
ts = ts.dt.tz_convert(None)
|
| 193 |
-
out = pd.DataFrame({"ts": ts.astype("datetime64[ns]"), "
|
| 194 |
return out.dropna().drop_duplicates("ts").sort_values("ts").reset_index(drop=True)
|
| 195 |
|
| 196 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 197 |
async def fetch_deriv_features(symbol, exchange, timeframe, df):
|
| 198 |
-
"""
|
| 199 |
-
Dönen: (özellik tablosu | None, df (
|
| 200 |
-
import sys
|
| 201 |
extra = {}
|
| 202 |
-
|
| 203 |
-
|
|
|
|
|
|
|
| 204 |
return None, df, extra
|
| 205 |
-
|
| 206 |
-
|
| 207 |
-
|
| 208 |
-
cwd=os.path.dirname(os.path.abspath(DERIV_PATH)),
|
| 209 |
-
env={**os.environ},
|
| 210 |
-
)
|
| 211 |
-
perp = _perp(symbol)
|
| 212 |
try:
|
| 213 |
-
async with
|
| 214 |
async with ClientSession(r, w) as session:
|
| 215 |
await session.initialize()
|
| 216 |
tools = {t.name: t for t in (await session.list_tools()).tools}
|
| 217 |
-
|
| 218 |
-
|
| 219 |
-
for name in ("get_funding_rate", "compare_funding", "get_open_interest",
|
| 220 |
-
"get_long_short_ratio", "get_liquidations"):
|
| 221 |
if name in tools:
|
| 222 |
try:
|
| 223 |
extra[f"deriv_{name}"] = await call_tool(
|
| 224 |
-
session, tools, name, symbol=
|
| 225 |
except Exception as e: # noqa: BLE001
|
| 226 |
extra[f"deriv_{name}"] = {"error": _root(e)}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 227 |
except Exception as e: # noqa: BLE001
|
| 228 |
extra["deriv_error"] = _root(e)
|
| 229 |
return None, df, extra
|
| 230 |
|
| 231 |
-
|
| 232 |
-
|
| 233 |
-
|
| 234 |
-
|
| 235 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 236 |
return None, df, extra
|
| 237 |
|
| 238 |
-
|
| 239 |
-
|
| 240 |
-
|
| 241 |
-
|
| 242 |
-
f["fund_ma9"] = rr.rolling(9).mean() * 1e4
|
| 243 |
-
f = f.dropna().reset_index(drop=True)
|
| 244 |
-
start = f["ts"].iloc[0]
|
| 245 |
-
d2 = df[df["ts"] >= start].reset_index(drop=True)
|
| 246 |
-
if len(d2) < 800:
|
| 247 |
-
extra["deriv_note"] = (f"Funding geçmişi mumların yalnızca {len(d2)} barını kapsıyor (<800); "
|
| 248 |
-
"özellik eklenmedi")
|
| 249 |
return None, df, extra
|
|
|
|
|
|
|
| 250 |
d2["ts"] = d2["ts"].astype("datetime64[ns]")
|
| 251 |
-
m =
|
|
|
|
|
|
|
| 252 |
m.index = d2.index
|
| 253 |
-
extra["
|
| 254 |
return m.drop(columns=["ts"]).add_prefix("dv_"), d2, extra
|
| 255 |
|
| 256 |
|
|
@@ -661,7 +694,7 @@ with gr.Blocks(title="Tickscope Tahmin") as demo:
|
|
| 661 |
with gr.Row():
|
| 662 |
horizon = gr.Slider(1, 24, value=4, step=1, label="Tahmin ufku (bar)")
|
| 663 |
limit = gr.Slider(300, 20000, value=5000, step=100, label="Mum sayısı (1000 üstü sayfalanır)")
|
| 664 |
-
use_deriv = gr.Checkbox(True, label="Türev verisi (
|
| 665 |
btn = gr.Button("Tahmin et", variant="primary")
|
| 666 |
out_md = gr.Markdown()
|
| 667 |
out_plot = gr.Plot()
|
|
|
|
| 147 |
return out.to_dict("records")
|
| 148 |
|
| 149 |
|
| 150 |
+
# ----------------------------------------------------------------- 2. MCP: LiquidVision (türev verisi)
|
| 151 |
+
# Uzak (HTTP) MCP, kurulum yok: https://liquidvision.app/mcp
|
| 152 |
+
# - Anahtarsız: yalnızca "sonuç" araçları (brief, positioning regime, cascades...) -> canlı bağlam.
|
| 153 |
+
# - Funding / OI GEÇMİŞİ (eğitim özelliği) ham-satır araçlarıdır; Pro/Bot anahtarı ister:
|
| 154 |
+
# LIQUIDVISION_API_KEY=lv_... (X-API-Key olarak gönderilir)
|
| 155 |
+
DERIV_URL = os.environ.get("LIQUIDVISION_URL", "https://liquidvision.app/mcp")
|
| 156 |
+
DERIV_KEY = os.environ.get("LIQUIDVISION_API_KEY", "")
|
| 157 |
+
LIVE_TOOLS = ["get_market_brief", "get_positioning_regime", "get_liquidation_cascades",
|
| 158 |
+
"get_microstructure", "get_funding_arbitrage", "get_funding_dispersion"]
|
| 159 |
+
|
| 160 |
+
|
| 161 |
+
def _usdt(symbol):
|
| 162 |
+
"""BTC/USDT veya BTC/USDT:USDT -> BTCUSDT"""
|
| 163 |
+
return symbol.strip().upper().split(":")[0].replace("/", "")
|
| 164 |
+
|
| 165 |
+
|
| 166 |
+
def _find_rows(o):
|
| 167 |
+
if isinstance(o, list) and len(o) >= 5 and all(isinstance(x, dict) for x in o[:3]):
|
| 168 |
+
return o
|
| 169 |
+
if isinstance(o, dict):
|
| 170 |
+
for v in o.values():
|
| 171 |
+
r = _find_rows(v)
|
| 172 |
+
if r:
|
| 173 |
+
return r
|
| 174 |
+
return None
|
|
|
|
|
|
|
| 175 |
|
| 176 |
+
|
| 177 |
+
def parse_series(payload, vkeys):
|
| 178 |
+
"""Geçmiş çıktısında (şemadan bağımsız) zaman + değer kolonlarını bulup ts/v tablosu döndürür."""
|
| 179 |
+
rows = _find_rows(payload)
|
| 180 |
if not rows:
|
| 181 |
return None
|
| 182 |
d = pd.DataFrame(rows)
|
| 183 |
+
tcol = next((c for c in ("timestamp", "time", "ts", "t", "datetime", "fundingTime", "ts_ms", "hour", "bucket")
|
| 184 |
+
if c in d), None)
|
| 185 |
+
vcol = next((c for c in vkeys if c in d), None)
|
| 186 |
+
if tcol is None or vcol is None:
|
| 187 |
return None
|
| 188 |
ts = d[tcol]
|
| 189 |
if pd.api.types.is_numeric_dtype(ts):
|
|
|
|
| 192 |
ts = pd.to_datetime(ts)
|
| 193 |
if getattr(ts.dt, "tz", None) is not None:
|
| 194 |
ts = ts.dt.tz_convert(None)
|
| 195 |
+
out = pd.DataFrame({"ts": ts.astype("datetime64[ns]"), "v": pd.to_numeric(d[vcol], errors="coerce")})
|
| 196 |
return out.dropna().drop_duplicates("ts").sort_values("ts").reset_index(drop=True)
|
| 197 |
|
| 198 |
|
| 199 |
+
def _funding_feats(f):
|
| 200 |
+
r = f["v"]
|
| 201 |
+
f["fund"] = r * 1e4 # baz puan
|
| 202 |
+
f["fund_z"] = (r - r.rolling(30).mean()) / (r.rolling(30).std() + 1e-12)
|
| 203 |
+
f["fund_chg"] = r.diff() * 1e4
|
| 204 |
+
f["fund_ma9"] = r.rolling(9).mean() * 1e4
|
| 205 |
+
return f.drop(columns=["v"]).dropna().reset_index(drop=True)
|
| 206 |
+
|
| 207 |
+
|
| 208 |
+
def _oi_feats(f):
|
| 209 |
+
o = f["v"]
|
| 210 |
+
for k in (1, 4, 24):
|
| 211 |
+
f[f"oi_chg{k}"] = o.pct_change(k)
|
| 212 |
+
f["oi_z"] = (o - o.rolling(48).mean()) / (o.rolling(48).std() + 1e-12)
|
| 213 |
+
return f.drop(columns=["v"]).replace([np.inf, -np.inf], np.nan).dropna().reset_index(drop=True)
|
| 214 |
+
|
| 215 |
+
|
| 216 |
async def fetch_deriv_features(symbol, exchange, timeframe, df):
|
| 217 |
+
"""LiquidVision MCP'den canlı bağlam (ham çıktıya) + funding/OI geçmişi (özellik) çeker.
|
| 218 |
+
Dönen: (özellik tablosu | None, df (geçmiş kapsamına kırpılmış olabilir), ham çıktı sözlüğü)."""
|
|
|
|
| 219 |
extra = {}
|
| 220 |
+
try:
|
| 221 |
+
from mcp.client.streamable_http import streamablehttp_client
|
| 222 |
+
except Exception as e: # noqa: BLE001
|
| 223 |
+
extra["deriv_error"] = f"mcp sürümü streamable HTTP desteklemiyor: {_root(e)} (pip install -U mcp)"
|
| 224 |
return None, df, extra
|
| 225 |
+
headers = {"X-API-Key": DERIV_KEY} if DERIV_KEY else {}
|
| 226 |
+
sym = _usdt(symbol)
|
| 227 |
+
hist = {}
|
|
|
|
|
|
|
|
|
|
|
|
|
| 228 |
try:
|
| 229 |
+
async with streamablehttp_client(DERIV_URL, headers=headers) as (r, w, _):
|
| 230 |
async with ClientSession(r, w) as session:
|
| 231 |
await session.initialize()
|
| 232 |
tools = {t.name: t for t in (await session.list_tools()).tools}
|
| 233 |
+
extra["deriv_available_tools"] = list(tools)
|
| 234 |
+
for name in LIVE_TOOLS:
|
|
|
|
|
|
|
| 235 |
if name in tools:
|
| 236 |
try:
|
| 237 |
extra[f"deriv_{name}"] = await call_tool(
|
| 238 |
+
session, tools, name, symbol=sym, exchange=exchange)
|
| 239 |
except Exception as e: # noqa: BLE001
|
| 240 |
extra[f"deriv_{name}"] = {"error": _root(e)}
|
| 241 |
+
for name in ("get_funding_history", "get_open_interest_history"):
|
| 242 |
+
if name in tools:
|
| 243 |
+
try:
|
| 244 |
+
hist[name] = await call_tool(session, tools, name, symbol=sym, exchange=exchange,
|
| 245 |
+
timeframe=timeframe, limit=1000)
|
| 246 |
+
except Exception as e: # noqa: BLE001
|
| 247 |
+
hist[name] = {"error": _root(e)}
|
| 248 |
except Exception as e: # noqa: BLE001
|
| 249 |
extra["deriv_error"] = _root(e)
|
| 250 |
return None, df, extra
|
| 251 |
|
| 252 |
+
specs = {
|
| 253 |
+
"get_funding_history": (("funding_rate", "fundingRate", "rate", "funding", "value"), _funding_feats),
|
| 254 |
+
"get_open_interest_history": (("open_interest", "oi", "openInterest", "oi_usd", "value"), _oi_feats),
|
| 255 |
+
}
|
| 256 |
+
feats = {}
|
| 257 |
+
for name, (vkeys, fn) in specs.items():
|
| 258 |
+
h = hist.get(name)
|
| 259 |
+
if h is None:
|
| 260 |
+
continue
|
| 261 |
+
if isinstance(h, dict) and "error" in h:
|
| 262 |
+
extra[f"deriv_{name}_error"] = _strip_series(h)
|
| 263 |
+
continue
|
| 264 |
+
s = parse_series(h, vkeys)
|
| 265 |
+
if s is None or len(s) < 80:
|
| 266 |
+
extra[f"deriv_{name}_raw"] = _strip_series(h) # şema beklenenden farklı
|
| 267 |
+
continue
|
| 268 |
+
feats[name] = fn(s)
|
| 269 |
+
if not feats:
|
| 270 |
+
extra["deriv_note"] = ("Geçmiş özellik eklenmedi (ham-satır araçları için Pro/Bot anahtarı gerekir: "
|
| 271 |
+
"LIQUIDVISION_API_KEY). Canlı bağlam ham çıktıda.")
|
| 272 |
return None, df, extra
|
| 273 |
|
| 274 |
+
# Geçmişin kapsamadığı eski mumları kırp; <800 bar kalıyorsa o seriyi at
|
| 275 |
+
usable = {k: v for k, v in feats.items() if (df["ts"] >= v["ts"].iloc[0]).sum() >= 800}
|
| 276 |
+
if not usable:
|
| 277 |
+
extra["deriv_note"] = "Geçmiş mum verisinin yalnızca küçük kısmını kapsıyor (<800 bar); özellik eklenmedi"
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 278 |
return None, df, extra
|
| 279 |
+
start = max(v["ts"].iloc[0] for v in usable.values())
|
| 280 |
+
d2 = df[df["ts"] >= start].reset_index(drop=True)
|
| 281 |
d2["ts"] = d2["ts"].astype("datetime64[ns]")
|
| 282 |
+
m = d2[["ts"]].copy()
|
| 283 |
+
for v in usable.values():
|
| 284 |
+
m = pd.merge_asof(m, v, on="ts", direction="backward") # lookahead yok
|
| 285 |
m.index = d2.index
|
| 286 |
+
extra["deriv_used"] = {"seriler": list(usable), "baslangic": str(start), "bar": len(d2)}
|
| 287 |
return m.drop(columns=["ts"]).add_prefix("dv_"), d2, extra
|
| 288 |
|
| 289 |
|
|
|
|
| 694 |
with gr.Row():
|
| 695 |
horizon = gr.Slider(1, 24, value=4, step=1, label="Tahmin ufku (bar)")
|
| 696 |
limit = gr.Slider(300, 20000, value=5000, step=100, label="Mum sayısı (1000 üstü sayfalanır)")
|
| 697 |
+
use_deriv = gr.Checkbox(True, label="Türev verisi (LiquidVision MCP: funding/OI) — mumları geçmişin kapsadığı aralığa kırpabilir")
|
| 698 |
btn = gr.Button("Tahmin et", variant="primary")
|
| 699 |
out_md = gr.Markdown()
|
| 700 |
out_plot = gr.Plot()
|