소스 검색

Add authenticated Bitstamp fee lookup

Lukas Goldschmidt 1 개월 전
부모
커밋
02ac5d0dd2
3개의 변경된 파일71개의 추가작업 그리고 1개의 파일을 삭제
  1. 3 0
      src/exec_mcp/bitstamp.py
  2. 14 1
      src/exec_mcp/server.py
  3. 54 0
      src/exec_mcp/services_bitstamp.py

+ 3 - 0
src/exec_mcp/bitstamp.py

@@ -119,6 +119,9 @@ class LG_Trading(Trading):
         url = self._construct_url("fees/trading/", base, quote)
         return self._post(url, return_json=True, version=2)
 
+    def fees_trading_market(self, market_symbol: str):
+        return self._post(f"fees/trading/{market_symbol.lower()}/", return_json=True, version=2)
+
 
 class BitstampClient:
     def __init__(self, username: str, api_key: str, api_secret: str):

+ 14 - 1
src/exec_mcp/server.py

@@ -9,7 +9,7 @@ from fastmcp import FastMCP
 
 from .models import AccountView
 from . import repo
-from .services_bitstamp import fetch_account_info as fetch_remote_account_info
+from .services_bitstamp import fetch_account_info as fetch_remote_account_info, fetch_account_fees as fetch_remote_account_fees
 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_private_ws import private_ws_main
@@ -273,6 +273,19 @@ def get_account_info(account_id: str) -> dict:
     raise HTTPException(status_code=400, detail="unsupported venue")
 
 
+@mcp.tool()
+def get_account_fees(account_id: str, market_symbol: str | None = None) -> dict:
+    """get_account_fees(account_id, market_symbol=None)
+
+    Return Bitstamp fee information for one account, optionally scoped to a
+    market symbol.
+    """
+    account = repo.get_account(account_id)
+    if account["venue"] == "bitstamp":
+        return fetch_remote_account_fees(account_id, market_symbol)
+    raise HTTPException(status_code=400, detail="unsupported venue")
+
+
 @mcp.tool()
 def place_order(account_id: str, market: str, side: str, order_type: str, amount, price=None, expire_time: int | None = None, client_id: str | None = None, client_order_id: str | None = None) -> dict:
     """place_order(account_id, market, side, order_type, amount, price=None, expire_time=None, client_id=None, client_order_id=None)

+ 54 - 0
src/exec_mcp/services_bitstamp.py

@@ -11,8 +11,10 @@ from . import repo
 from .bitstamp import BitstampClient
 from .bitstamp_fx import load_eur_usd
 
+BITSTAMP_BASE_URL = "https://www.bitstamp.net"
 BALANCE_CACHE_TTL_SECONDS = 20
 ACCOUNT_INFO_CACHE_TTL_SECONDS = 30
+FEES_CACHE_TTL_SECONDS = 10 * 60
 STALE_CACHE_TTL_SECONDS = 10 * 60
 _CACHE_LOCKS: dict[str, threading.Lock] = {}
 _CACHE_LOCKS_GUARD = threading.Lock()
@@ -49,6 +51,54 @@ def _stale_key(cache_key: str) -> str:
     return f"{cache_key}:stale"
 
 
+def _fee_cache_key(market_symbol: str | None = None) -> str:
+    return "bitstamp:fees:all" if market_symbol is None else f"bitstamp:fees:{market_symbol.lower()}"
+
+
+def _normalize_fee_entry(item: dict) -> dict:
+    fees = item.get("fees") or {}
+    return {
+        "currency_pair": item.get("currency_pair"),
+        "market": item.get("market"),
+        "fees": {
+            "maker": fees.get("maker"),
+            "taker": fees.get("taker"),
+        },
+    }
+
+
+def fetch_trading_fees(account_id: str, market_symbol: str | None = None) -> dict:
+    cache_key = _fee_cache_key(market_symbol)
+    cached = repo.cache_get(cache_key)
+    if cached is not None:
+        return cached
+
+    with _cache_lock(cache_key):
+        cached = repo.cache_get(cache_key)
+        if cached is not None:
+            return cached
+
+        try:
+            client = get_bitstamp_client(account_id)
+            if market_symbol:
+                payload = client.trading.fees_trading_market(market_symbol)
+            else:
+                payload = client.trading.fees_trading()
+        except Exception as exc:
+            _cache_error(cache_key, str(exc))
+            raise
+
+        if isinstance(payload, list):
+            result = {"source": "bitstamp", "market": market_symbol, "fees": [_normalize_fee_entry(item) for item in payload]}
+        elif isinstance(payload, dict):
+            result = {"source": "bitstamp", "market": market_symbol, **_normalize_fee_entry(payload)}
+        else:
+            result = {"source": "bitstamp", "market": market_symbol, "raw": payload}
+
+        repo.cache_put(cache_key, result, _ttl_from_env("BITSTAMP_FEES_CACHE_TTL_SECONDS", FEES_CACHE_TTL_SECONDS))
+        return result
+
+
 def _require_client() -> None:
     if bitstamp is None:
         raise RuntimeError("bitstamp-python-client dependency is not installed")
@@ -210,3 +260,7 @@ def fetch_account_info(account_id: str) -> dict:
         repo.cache_put(cache_key, result, _ttl_from_env("BITSTAMP_ACCOUNT_INFO_CACHE_TTL_SECONDS", ACCOUNT_INFO_CACHE_TTL_SECONDS))
         repo.cache_put(_stale_key(cache_key), result, STALE_CACHE_TTL_SECONDS)
         return result
+
+
+def fetch_account_fees(account_id: str, market_symbol: str | None = None) -> dict:
+    return fetch_trading_fees(account_id, market_symbol)