feat: 15 nuovi indicatori quant (common + deribit + bybit + macro + sentiment)
Common (mcp_common): - indicators.py: vol_cone, hurst_exponent, half_life_mean_reversion, garch11_forecast, autocorrelation, rolling_sharpe, var_cvar - options.py (nuovo): oi_weighted_skew, smile_asymmetry, atm_vs_wings_vol, dealer_gamma_profile, vanna_charm_aggregate - microstructure.py (nuovo): orderbook_imbalance (ratio + microprice + slope) - stats.py (nuovo): cointegration_test Engle-Granger + ADF helper Deribit (+6 tool MCP): - get_dealer_gamma_profile (net dealer gamma + flip level) - get_vanna_charm (vanna/charm aggregati pesati OI) - get_oi_weighted_skew, get_smile_asymmetry, get_atm_vs_wings_vol - get_orderbook_imbalance Bybit (+2 tool MCP): - get_orderbook_imbalance, get_basis_term_structure (futures dated curve) Macro (+2 tool MCP): - get_yield_curve_slope (2y10y/5y30y + butterfly + regime) - get_breakeven_inflation (FRED T5YIE/T10YIE/T5YIFR) Sentiment (+3 tool MCP): - get_funding_arb_spread (opportunità arb compatte annualizzate) - get_liquidation_heatmap (heuristic da OI delta + funding extreme, no feed paid Coinglass) - get_cointegration_pairs (Engle-Granger su coppie crypto Binance hourly) Tutto in TDD pure-Python (no numpy/scipy in mcp_common). README aggiornato con elenco completo. 442 test totali verdi. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -4,6 +4,7 @@ import asyncio
|
||||
from typing import Any
|
||||
|
||||
from mcp_common import indicators as ind
|
||||
from mcp_common import microstructure as micro
|
||||
from pybit.unified_trading import HTTP
|
||||
|
||||
|
||||
@@ -349,6 +350,74 @@ class BybitClient:
|
||||
for r in rows
|
||||
]
|
||||
|
||||
async def get_orderbook_imbalance(
|
||||
self,
|
||||
symbol: str,
|
||||
category: str = "linear",
|
||||
depth: int = 10,
|
||||
) -> dict:
|
||||
"""Microstructure: bid/ask imbalance ratio + microprice + slope."""
|
||||
ob = await self.get_orderbook(symbol=symbol, category=category, limit=max(depth, 50))
|
||||
result = micro.orderbook_imbalance(ob.get("bids") or [], ob.get("asks") or [], depth=depth)
|
||||
return {
|
||||
"symbol": symbol,
|
||||
"category": category,
|
||||
"depth": depth,
|
||||
**result,
|
||||
"timestamp": ob.get("timestamp"),
|
||||
}
|
||||
|
||||
async def get_basis_term_structure(self, asset: str) -> dict:
|
||||
"""Basis curve futures (dated) vs perp + spot. Filtra contratti future
|
||||
BTCUSDT / ETHUSDT con scadenza, calcola annualized basis per ognuno.
|
||||
"""
|
||||
import datetime as _dt
|
||||
|
||||
asset = asset.upper()
|
||||
spot = await self.get_ticker(f"{asset}USDT", category="spot")
|
||||
perp = await self.get_ticker(f"{asset}USDT", category="linear")
|
||||
sp = spot.get("last_price")
|
||||
pp = perp.get("last_price")
|
||||
|
||||
# Lista futures dated (linear/inverse)
|
||||
instr = await self.get_instruments(category="linear")
|
||||
items = (instr.get("instruments") or [])
|
||||
futures = [
|
||||
x for x in items
|
||||
if x.get("symbol", "").startswith(f"{asset}-") or x.get("symbol", "").startswith(f"{asset}USDT-")
|
||||
]
|
||||
|
||||
rows: list[dict[str, Any]] = []
|
||||
if sp:
|
||||
now_ms = int(_dt.datetime.now(_dt.UTC).timestamp() * 1000)
|
||||
for f in futures[:10]:
|
||||
tk = await self.get_ticker(f["symbol"], category="linear")
|
||||
fp = tk.get("last_price")
|
||||
expiry_ms = f.get("delivery_time")
|
||||
if not fp or not expiry_ms:
|
||||
continue
|
||||
days = max((int(expiry_ms) - now_ms) / 86_400_000, 1)
|
||||
basis_pct = 100.0 * (fp - sp) / sp
|
||||
annualized = basis_pct * 365.0 / days
|
||||
rows.append({
|
||||
"symbol": f["symbol"],
|
||||
"expiry_ms": int(expiry_ms),
|
||||
"days_to_expiry": round(days, 2),
|
||||
"future_price": fp,
|
||||
"basis_pct": round(basis_pct, 4),
|
||||
"annualized_basis_pct": round(annualized, 4),
|
||||
})
|
||||
|
||||
rows.sort(key=lambda r: r["days_to_expiry"])
|
||||
return {
|
||||
"asset": asset,
|
||||
"spot_price": sp,
|
||||
"perp_price": pp,
|
||||
"perp_basis_pct": round(100.0 * (pp - sp) / sp, 4) if (sp and pp) else None,
|
||||
"term_structure": rows,
|
||||
"data_timestamp": _dt.datetime.now(_dt.UTC).isoformat(),
|
||||
}
|
||||
|
||||
async def get_basis_spot_perp(self, asset: str) -> dict:
|
||||
asset = asset.upper()
|
||||
symbol = f"{asset}USDT"
|
||||
|
||||
@@ -100,6 +100,16 @@ class BasisSpotPerpReq(BaseModel):
|
||||
asset: str
|
||||
|
||||
|
||||
class OrderbookImbalanceReq(BaseModel):
|
||||
symbol: str
|
||||
category: str = "linear"
|
||||
depth: int = 10
|
||||
|
||||
|
||||
class BasisTermStructureReq(BaseModel):
|
||||
asset: str
|
||||
|
||||
|
||||
# --- Body models: writes ---
|
||||
|
||||
class PlaceOrderReq(BaseModel):
|
||||
@@ -311,6 +321,16 @@ def create_app(
|
||||
_check(principal, core=True, observer=True)
|
||||
return await client.get_basis_spot_perp(body.asset)
|
||||
|
||||
@app.post("/tools/get_orderbook_imbalance", tags=["reads"])
|
||||
async def t_get_ob_imbalance(body: OrderbookImbalanceReq, principal: Principal = Depends(require_principal)):
|
||||
_check(principal, core=True, observer=True)
|
||||
return await client.get_orderbook_imbalance(body.symbol, body.category, body.depth)
|
||||
|
||||
@app.post("/tools/get_basis_term_structure", tags=["reads"])
|
||||
async def t_get_basis_term_structure(body: BasisTermStructureReq, principal: Principal = Depends(require_principal)):
|
||||
_check(principal, core=True, observer=True)
|
||||
return await client.get_basis_term_structure(body.asset)
|
||||
|
||||
# ── Writes ─────────────────────────────────────────────
|
||||
|
||||
@app.post("/tools/place_order", tags=["writes"])
|
||||
@@ -403,6 +423,8 @@ def create_app(
|
||||
{"name": "get_trade_history", "description": "Fills recenti."},
|
||||
{"name": "get_open_orders", "description": "Ordini pending."},
|
||||
{"name": "get_basis_spot_perp", "description": "Basis spot vs linear perp."},
|
||||
{"name": "get_orderbook_imbalance", "description": "Microstructure: imbalance ratio + microprice + slope su top-N livelli book."},
|
||||
{"name": "get_basis_term_structure", "description": "Basis curve futures dated vs spot, annualizzato."},
|
||||
{"name": "place_order", "description": "Invia ordine (CORE only)."},
|
||||
{"name": "place_combo_order", "description": "Multi-leg atomico via place_batch_order (solo category=option)."},
|
||||
{"name": "amend_order", "description": "Modifica ordine esistente."},
|
||||
|
||||
Reference in New Issue
Block a user