Skip to content

Commit 5273ad8

Browse files
Guillermo Rodriguezgoodboy
authored andcommitted
binance: add deposits/withdrawals API support
From @guilledk, - Drop Decimal quantize for now - Minor tweaks to trades_dialogue proto
1 parent d1f1693 commit 5273ad8

File tree

1 file changed

+128
-61
lines changed

1 file changed

+128
-61
lines changed

piker/brokers/binance.py

Lines changed: 128 additions & 61 deletions
Original file line numberDiff line numberDiff line change
@@ -39,13 +39,15 @@
3939
)
4040
import hmac
4141
import time
42-
import decimal
4342
import hashlib
4443
from pathlib import Path
4544

4645
import trio
4746
from trio_typing import TaskStatus
48-
import pendulum
47+
from pendulum import (
48+
now,
49+
from_timestamp,
50+
)
4951
import asks
5052
from fuzzywuzzy import process as fuzzy
5153
import numpy as np
@@ -78,10 +80,10 @@
7880
from ..clearing._messages import (
7981
BrokerdOrder,
8082
BrokerdOrderAck,
81-
# BrokerdCancel,
82-
#BrokerdStatus,
83-
#BrokerdPosition,
84-
#BrokerdFill,
83+
BrokerdStatus,
84+
BrokerdPosition,
85+
BrokerdFill,
86+
BrokerdCancel,
8587
# BrokerdError,
8688
)
8789

@@ -104,6 +106,7 @@ def get_config() -> dict:
104106

105107

106108
_url = 'https://api.binance.com'
109+
_sapi_url = 'https://api.binance.com'
107110
_fapi_url = 'https://testnet.binancefuture.com'
108111

109112

@@ -243,18 +246,25 @@ def __init__(self) -> None:
243246
self._sesh = asks.Session(connections=4)
244247
self._sesh.base_location: str = _url
245248

246-
# testnet EP sesh
249+
# futes testnet rest EPs
247250
self._fapi_sesh = asks.Session(connections=4)
248251
self._fapi_sesh.base_location = _fapi_url
249252

253+
# sync rest API
254+
self._sapi_sesh = asks.Session(connections=4)
255+
self._sapi_sesh.base_location = _sapi_url
256+
250257
conf: dict = get_config()
251258
self.api_key: str = conf.get('api_key', '')
252259
self.api_secret: str = conf.get('api_secret', '')
253260

261+
self.watchlist = conf.get('watchlist', [])
262+
254263
if self.api_key:
255264
api_key_header = {'X-MBX-APIKEY': self.api_key}
256265
self._sesh.headers.update(api_key_header)
257266
self._fapi_sesh.headers.update(api_key_header)
267+
self._sapi_sesh.headers.update(api_key_header)
258268

259269
def _get_signature(self, data: OrderedDict) -> str:
260270

@@ -315,6 +325,25 @@ async def _fapi(
315325

316326
return resproc(resp, log)
317327

328+
async def _sapi(
329+
self,
330+
method: str,
331+
params: Union[dict, OrderedDict],
332+
signed: bool = False,
333+
action: str = 'get'
334+
) -> dict[str, Any]:
335+
336+
if signed:
337+
params['signature'] = self._get_signature(params)
338+
339+
resp = await getattr(self._sapi_sesh, action)(
340+
path=f'/sapi/v1/{method}',
341+
params=params,
342+
timeout=float('inf')
343+
)
344+
345+
return resproc(resp, log)
346+
318347
async def exch_info(
319348
self,
320349
sym: str | None = None,
@@ -397,7 +426,7 @@ async def bars(
397426
) -> dict:
398427

399428
if end_dt is None:
400-
end_dt = pendulum.now('UTC').add(minutes=1)
429+
end_dt = now('UTC').add(minutes=1)
401430

402431
if start_dt is None:
403432
start_dt = end_dt.start_of(
@@ -446,6 +475,58 @@ async def bars(
446475
array = np.array(new_bars, dtype=_ohlc_dtype) if as_np else bars
447476
return array
448477

478+
async def get_positions(
479+
self,
480+
recv_window: int = 60000
481+
) -> tuple:
482+
positions = {}
483+
volumes = {}
484+
485+
for sym in self.watchlist:
486+
log.info(f'doing {sym}...')
487+
params = OrderedDict([
488+
('symbol', sym),
489+
('recvWindow', recv_window),
490+
('timestamp', binance_timestamp(now()))
491+
])
492+
resp = await self._api(
493+
'allOrders',
494+
params=params,
495+
signed=True
496+
)
497+
log.info(f'done. len {len(resp)}')
498+
await trio.sleep(3)
499+
500+
return positions, volumes
501+
502+
async def get_deposits(
503+
self,
504+
recv_window: int = 60000
505+
) -> list:
506+
507+
params = OrderedDict([
508+
('recvWindow', recv_window),
509+
('timestamp', binance_timestamp(now()))
510+
])
511+
return await self._sapi(
512+
'capital/deposit/hisrec',
513+
params=params,
514+
signed=True)
515+
516+
async def get_withdrawls(
517+
self,
518+
recv_window: int = 60000
519+
) -> list:
520+
521+
params = OrderedDict([
522+
('recvWindow', recv_window),
523+
('timestamp', binance_timestamp(now()))
524+
])
525+
return await self._sapi(
526+
'capital/withdraw/history',
527+
params=params,
528+
signed=True)
529+
449530
async def submit_limit(
450531
self,
451532
symbol: str,
@@ -463,18 +544,8 @@ async def submit_limit(
463544

464545
await self.cache_symbols()
465546

466-
asset_precision = self._pairs[symbol]['baseAssetPrecision']
467-
quote_precision = self._pairs[symbol]['quoteAssetPrecision']
468-
469-
quantity = Decimal(quantity).quantize(
470-
Decimal(1 ** -asset_precision),
471-
rounding=decimal.ROUND_HALF_EVEN
472-
)
473-
474-
price = Decimal(price).quantize(
475-
Decimal(1 ** -quote_precision),
476-
rounding=decimal.ROUND_HALF_EVEN
477-
)
547+
# asset_precision = self._pairs[symbol]['baseAssetPrecision']
548+
# quote_precision = self._pairs[symbol]['quoteAssetPrecision']
478549

479550
params = OrderedDict([
480551
('symbol', symbol),
@@ -485,21 +556,21 @@ async def submit_limit(
485556
('price', price),
486557
('recvWindow', recv_window),
487558
('newOrderRespType', 'ACK'),
488-
('timestamp', binance_timestamp(pendulum.now()))
559+
('timestamp', binance_timestamp(now()))
489560
])
490561

491562
if oid:
492563
params['newClientOrderId'] = oid
493564

494565
resp = await self._api(
495-
'order/test', # TODO: switch to real `order` endpoint
566+
'order',
496567
params=params,
497568
signed=True,
498569
action='post'
499570
)
500-
501-
assert resp['orderId'] == oid
502-
return oid
571+
log.info(resp)
572+
# return resp['orderId']
573+
return resp['orderId']
503574

504575
async def submit_cancel(
505576
self,
@@ -513,22 +584,22 @@ async def submit_cancel(
513584
('symbol', symbol),
514585
('orderId', oid),
515586
('recvWindow', recv_window),
516-
('timestamp', binance_timestamp(pendulum.now()))
587+
('timestamp', binance_timestamp(now()))
517588
])
518589

519-
await self._api(
590+
return await self._api(
520591
'order',
521592
params=params,
522593
signed=True,
523594
action='delete'
524595
)
525596

526597
async def get_listen_key(self) -> str:
527-
return await self._api(
598+
return (await self._api(
528599
'userDataStream',
529600
params={},
530601
action='post'
531-
)['listenKey']
602+
))['listenKey']
532603

533604
async def keep_alive_key(self, listen_key: str) -> None:
534605
await self._fapi(
@@ -559,7 +630,7 @@ async def periodic_keep_alive(
559630
key = await self.get_listen_key()
560631

561632
async with trio.open_nursery() as n:
562-
n.start_soon(periodic_keep_alive, key)
633+
n.start_soon(periodic_keep_alive, self, key)
563634
yield key
564635
n.cancel_scope.cancel()
565636

@@ -730,8 +801,8 @@ async def get_ohlc(
730801
if (inow - times[-1]) > 60:
731802
await tractor.breakpoint()
732803

733-
start_dt = pendulum.from_timestamp(times[0])
734-
end_dt = pendulum.from_timestamp(times[-1])
804+
start_dt = from_timestamp(times[0])
805+
end_dt = from_timestamp(times[-1])
735806

736807
return array, start_dt, end_dt
737808

@@ -870,15 +941,15 @@ async def subscribe(ws: NoBsWs):
870941
# hz = 1/period if period else float('inf')
871942
# if hz > 60:
872943
# log.info(f'Binance quotez : {hz}')
873-
874-
topic = msg['symbol'].lower()
875-
await send_chan.send({topic: msg})
944+
945+
if typ == 'l1':
946+
topic = msg['symbol'].lower()
947+
await send_chan.send({topic: msg})
876948
# last = time.time()
877949

878950

879951
async def handle_order_requests(
880-
ems_order_stream: tractor.MsgStream,
881-
symbol: str
952+
ems_order_stream: tractor.MsgStream
882953
) -> None:
883954
async with open_cached_client('binance') as client:
884955
async for request_msg in ems_order_stream:
@@ -935,43 +1006,39 @@ async def trades_dialogue(
9351006
# ledger: TransactionLedger
9361007

9371008
# TODO: load pps and accounts using accounting apis!
938-
# positions: dict = {}
939-
# accounts: set[str] = set()
940-
# await ctx.started((positions, {}))
1009+
positions: list[BrokerdPosition] = []
1010+
accounts: list[str] = ['binance.default']
1011+
await ctx.started((positions, accounts))
9411012

9421013
async with (
9431014
ctx.open_stream() as ems_stream,
9441015
trio.open_nursery() as n,
9451016
open_cached_client('binance') as client,
946-
# client.manage_listen_key() as listen_key,
1017+
client.manage_listen_key() as listen_key,
9471018
):
9481019
n.start_soon(handle_order_requests, ems_stream)
949-
await trio.sleep_forever()
950-
1020+
# await trio.sleep_forever()
1021+
9511022
async with open_autorecon_ws(
9521023
f'wss://stream.binance.com:9443/ws/{listen_key}',
9531024
) as ws:
9541025
event = await ws.recv_msg()
9551026

1027+
# https://binance-docs.github.io/apidocs/spot/en/#payload-balance-update
9561028
if event.get('e') == 'executionReport':
957-
"""
958-
https://binance-docs.github.io/apidocs/spot/en/#payload-balance-update
959-
"""
960-
961-
oid = event.get('c')
962-
side = event.get('S').lower()
963-
status = event.get('X')
964-
order_qty = float(event.get('q'))
965-
filled_qty = float(event.get('z'))
966-
cumm_transacted_qty = float(event.get('Z'))
967-
price_avg = cum_transacted_qty / filled_qty
968-
969-
broker_time = float(event.get('T'))
970-
971-
commission_amount = float(event.get('n'))
972-
commission_asset = event.get('N')
9731029

974-
if status == 'TRADE':
1030+
oid: str = event.get('c')
1031+
side: str = event.get('S').lower()
1032+
status: str = event.get('X')
1033+
order_qty: float = float(event.get('q'))
1034+
filled_qty: float = float(event.get('z'))
1035+
cum_transacted_qty: float = float(event.get('Z'))
1036+
price_avg: float = cum_transacted_qty / filled_qty
1037+
broker_time: float = float(event.get('T'))
1038+
commission_amount: float = float(event.get('n'))
1039+
commission_asset: float = event.get('N')
1040+
1041+
if status == 'TRADE':
9751042
if order_qty == filled_qty:
9761043
msg = BrokerdFill(
9771044
reqid=oid,
@@ -990,7 +1057,7 @@ async def trades_dialogue(
9901057
)
9911058

9921059
else:
993-
if status == 'NEW':
1060+
if status == 'NEW':
9941061
status = 'submitted'
9951062

9961063
elif status == 'CANCELED':

0 commit comments

Comments
 (0)