Ver código fonte

Refine exec-mcp Bitstamp order flow

Lukas Goldschmidt 1 mês atrás
pai
commit
4db2f6f479
3 arquivos alterados com 38 adições e 8 exclusões
  1. 5 4
      README.md
  2. 8 0
      src/exec_mcp/bitstamp_metadata.py
  3. 25 4
      src/exec_mcp/server.py

+ 5 - 4
README.md

@@ -15,7 +15,8 @@ This service is the order-and-account layer of the stack. It owns exchange acces
 
 - List configured accounts, optionally filtered by exchange
 - Expose account metadata, balances, commissions, and exchange-specific limits
-- Keep the public MCP surface read-only for now
+- Expose read-only market/currency discovery helpers
+- Keep the public MCP surface read-only for discovery, with execution tools for orders
 - Use the dashboard for adding, updating, enabling, disabling, and deleting accounts
 - Store credentials separately from account metadata
 - Record balance snapshots over time internally
@@ -72,6 +73,6 @@ These are stored in SQLite for reuse across restarts.
 
 ## Bitstamp websocket prices
 
-The app also starts a Bitstamp websocket loop at startup and reconnects on failure.
-It subscribes to public `live_trades_[market]` channels for markets inferred from held assets in enabled Bitstamp accounts.
-Latest prices are persisted in SQLite for later valuation work.
+`exec-mcp` no longer owns public live-price streaming. Price lookup is expected to come from `crypto` MCP's cached `get_price` path when needed.
+
+`exec-mcp` keeps only the Bitstamp private websocket needed for order state updates.

+ 8 - 0
src/exec_mcp/bitstamp_metadata.py

@@ -48,6 +48,14 @@ def load_market_by_symbol(market_symbol: str) -> dict | None:
     return None
 
 
+def list_markets() -> list[dict]:
+    return load_metadata("markets")
+
+
+def list_currencies() -> list[dict]:
+    return load_metadata("currencies")
+
+
 def refresh_metadata() -> dict:
     currencies = fetch_currencies()
     markets = fetch_markets()

+ 25 - 4
src/exec_mcp/server.py

@@ -10,9 +10,8 @@ from fastmcp import FastMCP
 from .models import AccountView
 from . import repo
 from .services_bitstamp import fetch_account_info as fetch_remote_account_info
-from .bitstamp_metadata import METADATA_REFRESH_SECONDS, refresh_metadata
+from .bitstamp_metadata import METADATA_REFRESH_SECONDS, refresh_metadata, list_markets as bitstamp_list_markets, list_currencies as bitstamp_list_currencies
 from .bitstamp_fx import FX_REFRESH_SECONDS, refresh_eur_usd
-from .bitstamp_ws import ws_main
 from .bitstamp_private_ws import private_ws_main
 from .services_orders import place_order as service_place_order, query_order as service_query_order, cancel_order as service_cancel_order
 from .storage import init_db
@@ -48,7 +47,6 @@ async def lifespan(_: FastAPI):
     stop_event = asyncio.Event()
     metadata_task = asyncio.create_task(_metadata_refresh_loop())
     fx_task = asyncio.create_task(_fx_refresh_loop())
-    ws_task = asyncio.create_task(ws_main(stop_event))
     private_ws_task = asyncio.create_task(private_ws_main(stop_event))
     try:
         yield
@@ -56,7 +54,6 @@ async def lifespan(_: FastAPI):
         stop_event.set()
         metadata_task.cancel()
         fx_task.cancel()
-        ws_task.cancel()
         private_ws_task.cancel()
 
 
@@ -237,11 +234,28 @@ def http_dashboard_delete_account(account_id: str) -> RedirectResponse:
 
 @mcp.tool()
 def list_accounts(enabled_only: bool = True, venue: str | None = None) -> list[dict]:
+    """List configured accounts.
+
+    `enabled_only` defaults to true. If `venue` is omitted, all venues are returned.
+    """
     return repo.list_accounts(venue=venue, enabled_only=enabled_only)
 
 
+@mcp.tool()
+def list_markets() -> list[dict]:
+    """Return the cached Bitstamp markets metadata."""
+    return bitstamp_list_markets()
+
+
+@mcp.tool()
+def list_currencies() -> list[dict]:
+    """Return the cached Bitstamp currencies metadata."""
+    return bitstamp_list_currencies()
+
+
 @mcp.tool()
 def get_account_info(account_id: str) -> dict:
+    """Return account details and live valuation when available."""
     account = repo.get_account(account_id)
     if account["venue"] == "bitstamp":
         return fetch_remote_account_info(account_id)
@@ -250,16 +264,23 @@ def get_account_info(account_id: str) -> dict:
 
 @mcp.tool()
 def place_order(account_id: str, market: str, side: str, order_type: str, amount, price=None, expire_time: int | None = None, client_order_id: str | None = None) -> dict:
+    """Place a Bitstamp order.
+
+    `market` is a Bitstamp symbol like `xrpusd`, `amount` is base units, and
+    `expire_time` is relative seconds from now.
+    """
     return service_place_order(account_id=account_id, market=market, side=side, order_type=order_type, amount=amount, price=price, expire_time=expire_time, client_order_id=client_order_id)
 
 
 @mcp.tool()
 def query_order(account_id: str, order_id, client_order_id: str | None = None, omit_transactions: bool | None = None) -> dict:
+    """Query a Bitstamp order by order id."""
     return service_query_order(account_id=account_id, order_id=order_id, client_order_id=client_order_id, omit_transactions=omit_transactions)
 
 
 @mcp.tool()
 def cancel_order(account_id: str, order_id) -> dict:
+    """Cancel a Bitstamp order by order id."""
     return service_cancel_order(account_id=account_id, order_id=order_id)