3939)
4040import hmac
4141import time
42- import decimal
4342import hashlib
4443from pathlib import Path
4544
4645import trio
4746from trio_typing import TaskStatus
48- import pendulum
47+ from pendulum import (
48+ now ,
49+ from_timestamp ,
50+ )
4951import asks
5052from fuzzywuzzy import process as fuzzy
5153import numpy as np
7880from ..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
879951async 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