feat(mcp-deribit,mcp-bybit): add place_combo_order
Deribit: private/create_combo + place_order sul combo instrument → una sola crociata di spread invece di N (slippage atteso ridotto su strutture liquide). ACL core + leverage cap su tutti i leg. Bybit: place_batch_order su category=option (atomic multi-leg, 1 round-trip API). Reject su category != option (perp/linear non supportano batch nativo). orderLinkId auto-generato per leg. Tutti i test: deribit 48/48, bybit 123/123. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -188,6 +188,28 @@ class PlaceOrderReq(BaseModel):
|
||||
leverage: int | None = None # CER-016: None → default cap (3x)
|
||||
|
||||
|
||||
class ComboLeg(BaseModel):
|
||||
instrument_name: str
|
||||
direction: str # "buy" | "sell"
|
||||
ratio: int = 1
|
||||
|
||||
|
||||
class PlaceComboOrderReq(BaseModel):
|
||||
legs: list[ComboLeg]
|
||||
side: str # "buy" | "sell"
|
||||
amount: float
|
||||
type: str = "limit"
|
||||
price: float | None = None
|
||||
label: str | None = None
|
||||
leverage: int | None = None
|
||||
|
||||
@model_validator(mode="after")
|
||||
def _at_least_two_legs(self):
|
||||
if len(self.legs) < 2:
|
||||
raise ValueError("combo requires at least 2 legs")
|
||||
return self
|
||||
|
||||
|
||||
class CancelOrderReq(BaseModel):
|
||||
order_id: str
|
||||
|
||||
@@ -477,6 +499,27 @@ def create_app(
|
||||
label=body.label,
|
||||
)
|
||||
|
||||
@app.post("/tools/place_combo_order", tags=["writes"])
|
||||
async def t_place_combo_order(
|
||||
body: PlaceComboOrderReq, principal: Principal = Depends(require_principal)
|
||||
):
|
||||
_check(principal, core=True)
|
||||
lev = _enforce_leverage(body.leverage, creds=creds, exchange="deribit")
|
||||
if lev != cap_default:
|
||||
for leg in body.legs:
|
||||
try:
|
||||
await client.set_leverage(leg.instrument_name, lev)
|
||||
except Exception:
|
||||
pass
|
||||
return await client.place_combo_order(
|
||||
legs=[leg.model_dump() for leg in body.legs],
|
||||
side=body.side,
|
||||
amount=body.amount,
|
||||
type=body.type,
|
||||
price=body.price,
|
||||
label=body.label,
|
||||
)
|
||||
|
||||
@app.post("/tools/cancel_order", tags=["writes"])
|
||||
async def t_cancel_order(
|
||||
body: CancelOrderReq, principal: Principal = Depends(require_principal)
|
||||
@@ -537,6 +580,7 @@ def create_app(
|
||||
{"name": "get_technical_indicators", "description": "Indicatori tecnici (RSI, MACD, ATR, ADX)."},
|
||||
{"name": "get_realized_vol", "description": "Volatilità realizzata annualizzata (log-return std) BTC/ETH + spread IV−RV."},
|
||||
{"name": "place_order", "description": "Invia ordine (CORE only, testnet)."},
|
||||
{"name": "place_combo_order", "description": "Crea combo via private/create_combo + piazza ordine sul combo (1 cross spread invece di N)."},
|
||||
{"name": "cancel_order", "description": "Cancella ordine."},
|
||||
{"name": "set_stop_loss", "description": "Setta stop loss su posizione."},
|
||||
{"name": "set_take_profit", "description": "Setta take profit su posizione."},
|
||||
|
||||
Reference in New Issue
Block a user