GoshawkVortexAI commited on
Commit
6d785c6
·
verified ·
1 Parent(s): e738281

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +102 -69
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: crypto-market-data-mcp (türev verisi)
151
- # Kurulum: git clone https://github.com/eliasfire617/crypto-market-data-mcp
152
- # (kendi venv'inde) pip install -r requirements.txt
153
- # Sonra ortam değişkenleri:
154
- # CRYPTO_MCP_SERVER=/yol/crypto-market-data-mcp/server.py
155
- # CRYPTO_MCP_PYTHON=/yol/crypto-market-data-mcp/.venv/bin/python (opsiyonel; yoksa bu uygulamanın python'u)
156
- DERIV_PATH = os.environ.get("CRYPTO_MCP_SERVER", "")
157
-
158
-
159
- def _perp(symbol):
160
- s = symbol.strip().upper()
161
- if "/" in s and ":" not in s:
162
- s = f"{s}:{s.split('/')[1]}"
163
- return s
164
-
165
-
166
- def parse_funding(payload):
167
- """Funding geçmişi çıktısında (şemadan bağımsız) zaman + oran kolonlarını bulur."""
168
- def find_rows(o):
169
- if isinstance(o, list) and len(o) >= 5 and all(isinstance(x, dict) for x in o[:3]):
170
- return o
171
- if isinstance(o, dict):
172
- for v in o.values():
173
- r = find_rows(v)
174
- if r:
175
- return r
176
- return None
177
 
178
- rows = find_rows(payload)
 
 
 
179
  if not rows:
180
  return None
181
  d = pd.DataFrame(rows)
182
- tcol = next((c for c in ("timestamp", "fundingTime", "time", "ts", "datetime") if c in d), None)
183
- rcol = next((c for c in ("fundingRate", "funding_rate", "rate", "value") if c in d), None)
184
- if tcol is None or rcol is None:
 
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]"), "r": pd.to_numeric(d[rcol], errors="coerce")})
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
- """2. MCP'den funding geçmişi (özellik) + OI / long-short / likidasyon (canlı bağlam) çeker.
199
- Dönen: (özellik tablosu | None, df (funding kapsamına kırpılmış olabilir), ham çıktı sözlüğü)."""
200
- import sys
201
  extra = {}
202
- if not DERIV_PATH or not os.path.exists(DERIV_PATH):
203
- extra["deriv_note"] = "CRYPTO_MCP_SERVER ayarlı değil / dosya yok; türev verisi atlandı"
 
 
204
  return None, df, extra
205
- params = StdioServerParameters(
206
- command=os.environ.get("CRYPTO_MCP_PYTHON", sys.executable),
207
- args=[DERIV_PATH],
208
- cwd=os.path.dirname(os.path.abspath(DERIV_PATH)),
209
- env={**os.environ},
210
- )
211
- perp = _perp(symbol)
212
  try:
213
- async with stdio_client(params) as (r, w):
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
- hist = await call_tool(session, tools, "get_funding_rate_history",
218
- symbol=perp, exchange=exchange, limit=1000)
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=perp, exchange=exchange, timeframe=timeframe)
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
- bad = hist is None or (isinstance(hist, dict) and "error" in hist)
232
- f = None if bad else parse_funding(hist)
233
- if f is None or len(f) < 60:
234
- extra["deriv_note"] = "Funding geçmişi okunamadı veya yetersiz; özellik eklenmedi"
235
- extra["deriv_funding_raw"] = _strip_series(hist)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
236
  return None, df, extra
237
 
238
- rr = f["r"]
239
- f["fund"] = rr * 1e4 # baz puan
240
- f["fund_z"] = (rr - rr.rolling(30).mean()) / (rr.rolling(30).std() + 1e-12)
241
- f["fund_chg"] = rr.diff() * 1e4
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 = pd.merge_asof(d2[["ts"]], f.drop(columns=["r"]), on="ts", direction="backward") # lookahead yok
 
 
252
  m.index = d2.index
253
- extra["deriv_funding_used"] = {"funding_gozlem": len(f), "baslangic": str(start), "bar": len(d2)}
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 (funding; 2. MCP) — mumları funding geçmişinin kapsadığı aralığa kırpabilir")
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()