refactor: remove all futures-related capabilities

Delete USDT-M futures support since the user's Binance API key does not
support futures trading. This simplifies the CLI to spot-only:

- Remove futures client wrapper (um_futures_client.py)
- Remove futures trade commands and close position logic
- Simplify account service to spot-only (no market_type field)
- Remove futures references from opportunity service
- Update README and tests to reflect spot-only architecture
- Bump version to 2.0.7

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-04-16 20:10:15 +08:00
parent 680bd3d33c
commit 0f862957b0
11 changed files with 112 additions and 549 deletions

View File

@@ -148,116 +148,3 @@ def execute_spot_trade(
)
audit_event("trade_filled", {**_trade_log_payload(intent, payload, status=result["status"]), "response_payload": response})
return {"trade": result}
def execute_futures_trade(
config: dict[str, Any],
*,
side: str,
symbol: str,
qty: float,
order_type: str,
price: float | None,
reduce_only: bool,
dry_run: bool | None,
futures_client: Any,
) -> dict[str, Any]:
normalized_symbol = normalize_symbol(symbol)
order_type = order_type.upper()
side = side.upper()
is_dry_run = _default_dry_run(config, dry_run)
if qty <= 0:
raise RuntimeError("Futures orders require a positive --qty")
if order_type == "LIMIT" and price is None:
raise RuntimeError("Futures limit orders require --price")
payload: dict[str, Any] = {
"symbol": normalized_symbol,
"side": side,
"type": order_type,
"quantity": qty,
"reduceOnly": "true" if reduce_only else "false",
}
if price is not None:
payload["price"] = price
payload["timeInForce"] = "GTC"
intent = TradeIntent(
market_type="futures",
symbol=normalized_symbol,
side=side,
order_type=order_type,
qty=qty,
quote_amount=None,
price=price,
reduce_only=reduce_only,
dry_run=is_dry_run,
)
audit_event("trade_submitted", _trade_log_payload(intent, payload, status="submitted"))
if is_dry_run:
response = {"dry_run": True, "status": "DRY_RUN", "request": payload}
result = asdict(
TradeResult(
market_type="futures",
symbol=normalized_symbol,
side=side,
order_type=order_type,
status="DRY_RUN",
dry_run=True,
request_payload=payload,
response_payload=response,
)
)
audit_event("trade_filled", {**_trade_log_payload(intent, payload, status="DRY_RUN"), "response_payload": response})
return {"trade": result}
try:
response = futures_client.new_order(**payload)
except Exception as exc:
audit_event("trade_failed", _trade_log_payload(intent, payload, status="failed", error=str(exc)))
raise RuntimeError(f"Futures order failed: {exc}") from exc
result = asdict(
TradeResult(
market_type="futures",
symbol=normalized_symbol,
side=side,
order_type=order_type,
status=str(response.get("status", "UNKNOWN")),
dry_run=False,
request_payload=payload,
response_payload=response,
)
)
audit_event("trade_filled", {**_trade_log_payload(intent, payload, status=result["status"]), "response_payload": response})
return {"trade": result}
def close_futures_position(
config: dict[str, Any],
*,
symbol: str,
dry_run: bool | None,
futures_client: Any,
) -> dict[str, Any]:
normalized_symbol = normalize_symbol(symbol)
positions = futures_client.position_risk(normalized_symbol)
target = next((item for item in positions if normalize_symbol(item["symbol"]) == normalized_symbol), None)
if target is None:
raise RuntimeError(f"No futures position found for {normalized_symbol}")
position_amt = float(target.get("positionAmt", 0.0))
if position_amt == 0:
raise RuntimeError(f"No open futures position for {normalized_symbol}")
side = "SELL" if position_amt > 0 else "BUY"
return execute_futures_trade(
config,
side=side,
symbol=normalized_symbol,
qty=abs(position_amt),
order_type="MARKET",
price=None,
reduce_only=True,
dry_run=dry_run,
futures_client=futures_client,
)