Spaces:
Runtime error
Runtime error
File size: 4,595 Bytes
6ca1e7a | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 | """
Minimal async Deriv WebSocket client.
Supports the read-only operations needed by the strategy engine plus
contract buy for live mode. Token is only attached when the operation
requires it (balance, buy, portfolio).
Docs: https://api.deriv.com/api-explorer/
"""
from __future__ import annotations
import asyncio
import json
import uuid
from contextlib import asynccontextmanager
from typing import Any, Optional
import websockets
from .config import get_settings
from .schemas import Candle
class DerivError(RuntimeError):
pass
class DerivClient:
def __init__(self, app_id: Optional[str] = None,
token: Optional[str] = None):
s = get_settings()
self.app_id = app_id or s.DERIV_APP_ID
self.token = token # may be None for public ops
self.url = f"{s.DERIV_WS_URL}?app_id={self.app_id}"
@asynccontextmanager
async def _connect(self):
async with websockets.connect(self.url, max_size=2**22) as ws:
if self.token:
await self._call(ws, {"authorize": self.token})
yield ws
async def _call(self, ws, payload: dict[str, Any]) -> dict[str, Any]:
req_id = payload.get("req_id") or str(uuid.uuid4())
payload = {**payload, "req_id": req_id}
await ws.send(json.dumps(payload))
while True:
msg = json.loads(await ws.recv())
if msg.get("req_id") == req_id or msg.get("echo_req", {}).get("req_id") == req_id:
if "error" in msg:
raise DerivError(msg["error"].get("message", "Deriv error"))
return msg
# ---------- public ----------
async def candles(self, symbol: str, granularity: int = 60,
count: int = 200) -> list[Candle]:
async with self._connect() as ws:
resp = await self._call(ws, {
"ticks_history": symbol,
"adjust_start_time": 1,
"count": count,
"end": "latest",
"granularity": granularity,
"style": "candles",
})
rows = resp.get("candles", [])
return [Candle(epoch=r["epoch"], open=float(r["open"]),
high=float(r["high"]), low=float(r["low"]),
close=float(r["close"]), volume=float(r.get("volume", 0)))
for r in rows]
async def tick(self, symbol: str) -> dict[str, Any]:
async with self._connect() as ws:
resp = await self._call(ws, {"ticks": symbol})
return resp.get("tick", {})
async def active_symbols(self) -> list[dict[str, Any]]:
async with self._connect() as ws:
resp = await self._call(ws, {
"active_symbols": "brief", "product_type": "basic"})
return resp.get("active_symbols", [])
# ---------- authenticated ----------
async def balance(self) -> dict[str, Any]:
if not self.token:
raise DerivError("Token required for balance()")
async with self._connect() as ws:
resp = await self._call(ws, {"balance": 1})
return resp.get("balance", {})
async def buy_contract(self, *, symbol: str, contract_type: str,
amount: float, duration: int = 5,
duration_unit: str = "m",
currency: str = "USD") -> dict[str, Any]:
"""
contract_type: 'CALL' (BUY) or 'PUT' (SELL) for rise/fall contracts.
"""
if not self.token:
raise DerivError("Token required for buy_contract()")
proposal = {
"proposal": 1, "amount": amount, "basis": "stake",
"contract_type": contract_type, "currency": currency,
"duration": duration, "duration_unit": duration_unit,
"symbol": symbol,
}
async with self._connect() as ws:
p = await self._call(ws, proposal)
prop = p.get("proposal", {})
if "id" not in prop:
raise DerivError("No proposal id returned")
b = await self._call(ws, {"buy": prop["id"], "price": amount})
return b.get("buy", {})
async def portfolio(self) -> dict[str, Any]:
if not self.token:
raise DerivError("Token required for portfolio()")
async with self._connect() as ws:
resp = await self._call(ws, {"portfolio": 1})
return resp.get("portfolio", {})
|