Compare commits
No commits in common. "ab1463d9429b0e8e9708a0dfdbc461cad1cf4b5e" and "b7883325a9e1ce9136f757c2fe9d3c8bbe597949" have entirely different histories.
ab1463d942
...
b7883325a9
|
@ -1,8 +1,8 @@
|
||||||
# piker: trading gear for hackers
|
# piker: trading gear for hackers
|
||||||
# Copyright (C)
|
# Copyright (C)
|
||||||
# Guillermo Rodriguez (aka ze jefe)
|
# Guillermo Rodriguez (aka ze jefe)
|
||||||
# Tyler Goodlet
|
# Tyler Goodlet
|
||||||
# (in stewardship for pikers)
|
# (in stewardship for pikers)
|
||||||
|
|
||||||
# This program is free software: you can redistribute it and/or modify
|
# This program is free software: you can redistribute it and/or modify
|
||||||
# it under the terms of the GNU Affero General Public License as published by
|
# it under the terms of the GNU Affero General Public License as published by
|
||||||
|
@ -25,13 +25,14 @@ from __future__ import annotations
|
||||||
from collections import ChainMap
|
from collections import ChainMap
|
||||||
from contextlib import (
|
from contextlib import (
|
||||||
asynccontextmanager as acm,
|
asynccontextmanager as acm,
|
||||||
AsyncExitStack,
|
|
||||||
)
|
)
|
||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
from pprint import pformat
|
from pprint import pformat
|
||||||
from typing import (
|
from typing import (
|
||||||
Any,
|
Any,
|
||||||
Callable,
|
Callable,
|
||||||
|
Hashable,
|
||||||
|
Sequence,
|
||||||
Type,
|
Type,
|
||||||
)
|
)
|
||||||
import hmac
|
import hmac
|
||||||
|
@ -42,7 +43,8 @@ import trio
|
||||||
from pendulum import (
|
from pendulum import (
|
||||||
now,
|
now,
|
||||||
)
|
)
|
||||||
import httpx
|
import asks
|
||||||
|
from rapidfuzz import process as fuzzy
|
||||||
import numpy as np
|
import numpy as np
|
||||||
|
|
||||||
from piker import config
|
from piker import config
|
||||||
|
@ -52,7 +54,6 @@ from piker.clearing._messages import (
|
||||||
from piker.accounting import (
|
from piker.accounting import (
|
||||||
Asset,
|
Asset,
|
||||||
digits_to_dec,
|
digits_to_dec,
|
||||||
MktPair,
|
|
||||||
)
|
)
|
||||||
from piker.types import Struct
|
from piker.types import Struct
|
||||||
from piker.data import (
|
from piker.data import (
|
||||||
|
@ -68,6 +69,7 @@ from .venues import (
|
||||||
PAIRTYPES,
|
PAIRTYPES,
|
||||||
Pair,
|
Pair,
|
||||||
MarketType,
|
MarketType,
|
||||||
|
|
||||||
_spot_url,
|
_spot_url,
|
||||||
_futes_url,
|
_futes_url,
|
||||||
_testnet_futes_url,
|
_testnet_futes_url,
|
||||||
|
@ -77,18 +79,19 @@ from .venues import (
|
||||||
log = get_logger('piker.brokers.binance')
|
log = get_logger('piker.brokers.binance')
|
||||||
|
|
||||||
|
|
||||||
def get_config() -> dict[str, Any]:
|
def get_config() -> dict:
|
||||||
|
|
||||||
conf: dict
|
conf: dict
|
||||||
path: Path
|
path: Path
|
||||||
conf, path = config.load(
|
conf, path = config.load(
|
||||||
conf_name='brokers',
|
conf_name='brokers',
|
||||||
touch_if_dne=True,
|
touch_if_dne=True,
|
||||||
)
|
)
|
||||||
section: dict = conf.get('binance')
|
|
||||||
|
section = conf.get('binance')
|
||||||
|
|
||||||
if not section:
|
if not section:
|
||||||
log.warning(
|
log.warning(f'No config section found for binance in {path}')
|
||||||
f'No config section found for binance in {path}'
|
|
||||||
)
|
|
||||||
return {}
|
return {}
|
||||||
|
|
||||||
return section
|
return section
|
||||||
|
@ -144,7 +147,7 @@ def binance_timestamp(
|
||||||
|
|
||||||
class Client:
|
class Client:
|
||||||
'''
|
'''
|
||||||
Async ReST API client using `trio` + `httpx` B)
|
Async ReST API client using ``trio`` + ``asks`` B)
|
||||||
|
|
||||||
Supports all of the spot, margin and futures endpoints depending
|
Supports all of the spot, margin and futures endpoints depending
|
||||||
on method.
|
on method.
|
||||||
|
@ -153,17 +156,10 @@ class Client:
|
||||||
def __init__(
|
def __init__(
|
||||||
self,
|
self,
|
||||||
|
|
||||||
venue_sessions: dict[
|
|
||||||
str, # venue key
|
|
||||||
tuple[httpx.AsyncClient, str] # session, eps path
|
|
||||||
],
|
|
||||||
conf: dict[str, Any],
|
|
||||||
# TODO: change this to `Client.[mkt_]venue: MarketType`?
|
# TODO: change this to `Client.[mkt_]venue: MarketType`?
|
||||||
mkt_mode: MarketType = 'spot',
|
mkt_mode: MarketType = 'spot',
|
||||||
|
|
||||||
) -> None:
|
) -> None:
|
||||||
self.conf = conf
|
|
||||||
|
|
||||||
# build out pair info tables for each market type
|
# build out pair info tables for each market type
|
||||||
# and wrap in a chain-map view for search / query.
|
# and wrap in a chain-map view for search / query.
|
||||||
self._spot_pairs: dict[str, Pair] = {} # spot info table
|
self._spot_pairs: dict[str, Pair] = {} # spot info table
|
||||||
|
@ -190,13 +186,44 @@ class Client:
|
||||||
# market symbols for use by search. See `.exch_info()`.
|
# market symbols for use by search. See `.exch_info()`.
|
||||||
self._pairs: ChainMap[str, Pair] = ChainMap()
|
self._pairs: ChainMap[str, Pair] = ChainMap()
|
||||||
|
|
||||||
|
# spot EPs sesh
|
||||||
|
self._sesh = asks.Session(connections=4)
|
||||||
|
self._sesh.base_location: str = _spot_url
|
||||||
|
# spot testnet
|
||||||
|
self._test_sesh: asks.Session = asks.Session(connections=4)
|
||||||
|
self._test_sesh.base_location: str = _testnet_spot_url
|
||||||
|
|
||||||
|
# margin and extended spot endpoints session.
|
||||||
|
self._sapi_sesh = asks.Session(connections=4)
|
||||||
|
self._sapi_sesh.base_location: str = _spot_url
|
||||||
|
|
||||||
|
# futes EPs sesh
|
||||||
|
self._fapi_sesh = asks.Session(connections=4)
|
||||||
|
self._fapi_sesh.base_location: str = _futes_url
|
||||||
|
# futes testnet
|
||||||
|
self._test_fapi_sesh: asks.Session = asks.Session(connections=4)
|
||||||
|
self._test_fapi_sesh.base_location: str = _testnet_futes_url
|
||||||
|
|
||||||
# global client "venue selection" mode.
|
# global client "venue selection" mode.
|
||||||
# set this when you want to switch venues and not have to
|
# set this when you want to switch venues and not have to
|
||||||
# specify the venue for the next request.
|
# specify the venue for the next request.
|
||||||
self.mkt_mode: MarketType = mkt_mode
|
self.mkt_mode: MarketType = mkt_mode
|
||||||
|
|
||||||
# per-mkt-venue API client table
|
# per 8
|
||||||
self.venue_sesh = venue_sessions
|
self.venue_sesh: dict[
|
||||||
|
str, # venue key
|
||||||
|
tuple[asks.Session, str] # session, eps path
|
||||||
|
] = {
|
||||||
|
'spot': (self._sesh, '/api/v3/'),
|
||||||
|
'spot_testnet': (self._test_sesh, '/fapi/v1/'),
|
||||||
|
|
||||||
|
'margin': (self._sapi_sesh, '/sapi/v1/'),
|
||||||
|
|
||||||
|
'usdtm_futes': (self._fapi_sesh, '/fapi/v1/'),
|
||||||
|
'usdtm_futes_testnet': (self._test_fapi_sesh, '/fapi/v1/'),
|
||||||
|
|
||||||
|
# 'futes_coin': self._dapi, # TODO
|
||||||
|
}
|
||||||
|
|
||||||
# lookup for going from `.mkt_mode: str` to the config
|
# lookup for going from `.mkt_mode: str` to the config
|
||||||
# subsection `key: str`
|
# subsection `key: str`
|
||||||
|
@ -211,6 +238,40 @@ class Client:
|
||||||
'futes': ['usdtm_futes'],
|
'futes': ['usdtm_futes'],
|
||||||
}
|
}
|
||||||
|
|
||||||
|
# for creating API keys see,
|
||||||
|
# https://www.binance.com/en/support/faq/how-to-create-api-keys-on-binance-360002502072
|
||||||
|
self.conf: dict = get_config()
|
||||||
|
|
||||||
|
for key, subconf in self.conf.items():
|
||||||
|
if api_key := subconf.get('api_key', ''):
|
||||||
|
venue_keys: list[str] = self.confkey2venuekeys[key]
|
||||||
|
|
||||||
|
venue_key: str
|
||||||
|
sesh: asks.Session
|
||||||
|
for venue_key in venue_keys:
|
||||||
|
sesh, _ = self.venue_sesh[venue_key]
|
||||||
|
|
||||||
|
api_key_header: dict = {
|
||||||
|
# taken from official:
|
||||||
|
# https://github.com/binance/binance-futures-connector-python/blob/main/binance/api.py#L47
|
||||||
|
"Content-Type": "application/json;charset=utf-8",
|
||||||
|
|
||||||
|
# TODO: prolly should just always query and copy
|
||||||
|
# in the real latest ver?
|
||||||
|
"User-Agent": "binance-connector/6.1.6smbz6",
|
||||||
|
"X-MBX-APIKEY": api_key,
|
||||||
|
}
|
||||||
|
sesh.headers.update(api_key_header)
|
||||||
|
|
||||||
|
# if `.use_tesnet = true` in the config then
|
||||||
|
# also add headers for the testnet session which
|
||||||
|
# will be used for all order control
|
||||||
|
if subconf.get('use_testnet', False):
|
||||||
|
testnet_sesh, _ = self.venue_sesh[
|
||||||
|
venue_key + '_testnet'
|
||||||
|
]
|
||||||
|
testnet_sesh.headers.update(api_key_header)
|
||||||
|
|
||||||
def _mk_sig(
|
def _mk_sig(
|
||||||
self,
|
self,
|
||||||
data: dict,
|
data: dict,
|
||||||
|
@ -229,6 +290,7 @@ class Client:
|
||||||
'to define the creds for auth-ed endpoints!?'
|
'to define the creds for auth-ed endpoints!?'
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
# XXX: Info on security and authentification
|
# XXX: Info on security and authentification
|
||||||
# https://binance-docs.github.io/apidocs/#endpoint-security-type
|
# https://binance-docs.github.io/apidocs/#endpoint-security-type
|
||||||
if not (api_secret := subconf.get('api_secret')):
|
if not (api_secret := subconf.get('api_secret')):
|
||||||
|
@ -257,7 +319,7 @@ class Client:
|
||||||
params: dict,
|
params: dict,
|
||||||
|
|
||||||
method: str = 'get',
|
method: str = 'get',
|
||||||
venue: str|None = None, # if None use `.mkt_mode` state
|
venue: str | None = None, # if None use `.mkt_mode` state
|
||||||
signed: bool = False,
|
signed: bool = False,
|
||||||
allow_testnet: bool = False,
|
allow_testnet: bool = False,
|
||||||
|
|
||||||
|
@ -268,9 +330,8 @@ class Client:
|
||||||
- /fapi/v3/ USD-M FUTURES, or
|
- /fapi/v3/ USD-M FUTURES, or
|
||||||
- /api/v3/ SPOT/MARGIN
|
- /api/v3/ SPOT/MARGIN
|
||||||
|
|
||||||
account/market endpoint request depending on either passed in
|
account/market endpoint request depending on either passed in `venue: str`
|
||||||
`venue: str` or the current setting `.mkt_mode: str` setting,
|
or the current setting `.mkt_mode: str` setting, default `'spot'`.
|
||||||
default `'spot'`.
|
|
||||||
|
|
||||||
|
|
||||||
Docs per venue API:
|
Docs per venue API:
|
||||||
|
@ -299,6 +360,9 @@ class Client:
|
||||||
venue=venue_key,
|
venue=venue_key,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
sesh: asks.Session
|
||||||
|
path: str
|
||||||
|
|
||||||
# Check if we're configured to route order requests to the
|
# Check if we're configured to route order requests to the
|
||||||
# venue equivalent's testnet.
|
# venue equivalent's testnet.
|
||||||
use_testnet: bool = False
|
use_testnet: bool = False
|
||||||
|
@ -323,12 +387,11 @@ class Client:
|
||||||
# ctl machinery B)
|
# ctl machinery B)
|
||||||
venue_key += '_testnet'
|
venue_key += '_testnet'
|
||||||
|
|
||||||
client: httpx.AsyncClient
|
sesh, path = self.venue_sesh[venue_key]
|
||||||
path: str
|
|
||||||
client, path = self.venue_sesh[venue_key]
|
meth: Callable = getattr(sesh, method)
|
||||||
meth: Callable = getattr(client, method)
|
|
||||||
resp = await meth(
|
resp = await meth(
|
||||||
url=path + endpoint,
|
path=path + endpoint,
|
||||||
params=params,
|
params=params,
|
||||||
timeout=float('inf'),
|
timeout=float('inf'),
|
||||||
)
|
)
|
||||||
|
@ -370,15 +433,7 @@ class Client:
|
||||||
item['filters'] = filters
|
item['filters'] = filters
|
||||||
|
|
||||||
pair_type: Type = PAIRTYPES[venue]
|
pair_type: Type = PAIRTYPES[venue]
|
||||||
try:
|
pair: Pair = pair_type(**item)
|
||||||
pair: Pair = pair_type(**item)
|
|
||||||
except Exception as e:
|
|
||||||
e.add_note(
|
|
||||||
"\nDon't panic, prolly stupid binance changed their symbology schema again..\n"
|
|
||||||
'Check out their API docs here:\n\n'
|
|
||||||
'https://binance-docs.github.io/apidocs/spot/en/#exchange-information'
|
|
||||||
)
|
|
||||||
raise
|
|
||||||
pair_table[pair.symbol.upper()] = pair
|
pair_table[pair.symbol.upper()] = pair
|
||||||
|
|
||||||
# update an additional top-level-cross-venue-table
|
# update an additional top-level-cross-venue-table
|
||||||
|
@ -473,9 +528,7 @@ class Client:
|
||||||
|
|
||||||
'''
|
'''
|
||||||
pair_table: dict[str, Pair] = self._venue2pairs[
|
pair_table: dict[str, Pair] = self._venue2pairs[
|
||||||
venue
|
venue or self.mkt_mode
|
||||||
or
|
|
||||||
self.mkt_mode
|
|
||||||
]
|
]
|
||||||
if (
|
if (
|
||||||
expiry
|
expiry
|
||||||
|
@ -494,9 +547,9 @@ class Client:
|
||||||
venues: list[str] = [venue]
|
venues: list[str] = [venue]
|
||||||
|
|
||||||
# batch per-venue download of all exchange infos
|
# batch per-venue download of all exchange infos
|
||||||
async with trio.open_nursery() as tn:
|
async with trio.open_nursery() as rn:
|
||||||
for ven in venues:
|
for ven in venues:
|
||||||
tn.start_soon(
|
rn.start_soon(
|
||||||
self._cache_pairs,
|
self._cache_pairs,
|
||||||
ven,
|
ven,
|
||||||
)
|
)
|
||||||
|
@ -549,11 +602,11 @@ class Client:
|
||||||
|
|
||||||
) -> dict[str, Any]:
|
) -> dict[str, Any]:
|
||||||
|
|
||||||
fq_pairs: dict[str, Pair] = await self.exch_info()
|
fq_pairs: dict = await self.exch_info()
|
||||||
|
|
||||||
# TODO: cache this list like we were in
|
# TODO: cache this list like we were in
|
||||||
# `open_symbol_search()`?
|
# `open_symbol_search()`?
|
||||||
# keys: list[str] = list(fq_pairs)
|
keys: list[str] = list(fq_pairs)
|
||||||
|
|
||||||
return match_from_pairs(
|
return match_from_pairs(
|
||||||
pairs=fq_pairs,
|
pairs=fq_pairs,
|
||||||
|
@ -561,19 +614,9 @@ class Client:
|
||||||
score_cutoff=50,
|
score_cutoff=50,
|
||||||
)
|
)
|
||||||
|
|
||||||
def pair2venuekey(
|
|
||||||
self,
|
|
||||||
pair: Pair,
|
|
||||||
) -> str:
|
|
||||||
return {
|
|
||||||
'USDTM': 'usdtm_futes',
|
|
||||||
# 'COINM': 'coin_futes',
|
|
||||||
# ^-TODO-^ bc someone might want it..?
|
|
||||||
}[pair.venue]
|
|
||||||
|
|
||||||
async def bars(
|
async def bars(
|
||||||
self,
|
self,
|
||||||
mkt: MktPair,
|
symbol: str,
|
||||||
|
|
||||||
start_dt: datetime | None = None,
|
start_dt: datetime | None = None,
|
||||||
end_dt: datetime | None = None,
|
end_dt: datetime | None = None,
|
||||||
|
@ -603,20 +646,16 @@ class Client:
|
||||||
start_time = binance_timestamp(start_dt)
|
start_time = binance_timestamp(start_dt)
|
||||||
end_time = binance_timestamp(end_dt)
|
end_time = binance_timestamp(end_dt)
|
||||||
|
|
||||||
bs_pair: Pair = self._pairs[mkt.bs_fqme.upper()]
|
|
||||||
|
|
||||||
# https://binance-docs.github.io/apidocs/spot/en/#kline-candlestick-data
|
# https://binance-docs.github.io/apidocs/spot/en/#kline-candlestick-data
|
||||||
bars = await self._api(
|
bars = await self._api(
|
||||||
'klines',
|
'klines',
|
||||||
params={
|
params={
|
||||||
# NOTE: always query using their native symbology!
|
'symbol': symbol.upper(),
|
||||||
'symbol': mkt.bs_mktid.upper(),
|
|
||||||
'interval': '1m',
|
'interval': '1m',
|
||||||
'startTime': start_time,
|
'startTime': start_time,
|
||||||
'endTime': end_time,
|
'endTime': end_time,
|
||||||
'limit': limit
|
'limit': limit
|
||||||
},
|
},
|
||||||
venue=self.pair2venuekey(bs_pair),
|
|
||||||
allow_testnet=False,
|
allow_testnet=False,
|
||||||
)
|
)
|
||||||
new_bars: list[tuple] = []
|
new_bars: list[tuple] = []
|
||||||
|
@ -933,148 +972,17 @@ class Client:
|
||||||
await self.close_listen_key(key)
|
await self.close_listen_key(key)
|
||||||
|
|
||||||
|
|
||||||
_venue_urls: dict[str, str] = {
|
|
||||||
'spot': (
|
|
||||||
_spot_url,
|
|
||||||
'/api/v3/',
|
|
||||||
),
|
|
||||||
'spot_testnet': (
|
|
||||||
_testnet_spot_url,
|
|
||||||
'/fapi/v1/'
|
|
||||||
),
|
|
||||||
# margin and extended spot endpoints session.
|
|
||||||
# TODO: did this ever get implemented fully?
|
|
||||||
# 'margin': (
|
|
||||||
# _spot_url,
|
|
||||||
# '/sapi/v1/'
|
|
||||||
# ),
|
|
||||||
|
|
||||||
'usdtm_futes': (
|
|
||||||
_futes_url,
|
|
||||||
'/fapi/v1/',
|
|
||||||
),
|
|
||||||
|
|
||||||
'usdtm_futes_testnet': (
|
|
||||||
_testnet_futes_url,
|
|
||||||
'/fapi/v1/',
|
|
||||||
),
|
|
||||||
|
|
||||||
# TODO: for anyone who actually needs it ;P
|
|
||||||
# 'coin_futes': ()
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
def init_api_keys(
|
|
||||||
client: Client,
|
|
||||||
conf: dict[str, Any],
|
|
||||||
) -> None:
|
|
||||||
'''
|
|
||||||
Set up per-venue API keys each http client according to the user's
|
|
||||||
`brokers.conf`.
|
|
||||||
|
|
||||||
For ex, to use spot-testnet and live usdt futures APIs:
|
|
||||||
|
|
||||||
```toml
|
|
||||||
[binance]
|
|
||||||
# spot test net
|
|
||||||
spot.use_testnet = true
|
|
||||||
spot.api_key = '<spot_api_key_from_binance_account>'
|
|
||||||
spot.api_secret = '<spot_api_key_password>'
|
|
||||||
|
|
||||||
# futes live
|
|
||||||
futes.use_testnet = false
|
|
||||||
accounts.usdtm = 'futes'
|
|
||||||
futes.api_key = '<futes_api_key_from_binance>'
|
|
||||||
futes.api_secret = '<futes_api_key_password>''
|
|
||||||
|
|
||||||
# if uncommented will use the built-in paper engine and not
|
|
||||||
# connect to `binance` API servers for order ctl.
|
|
||||||
# accounts.paper = 'paper'
|
|
||||||
```
|
|
||||||
|
|
||||||
'''
|
|
||||||
for key, subconf in conf.items():
|
|
||||||
if api_key := subconf.get('api_key', ''):
|
|
||||||
venue_keys: list[str] = client.confkey2venuekeys[key]
|
|
||||||
|
|
||||||
venue_key: str
|
|
||||||
client: httpx.AsyncClient
|
|
||||||
for venue_key in venue_keys:
|
|
||||||
client, _ = client.venue_sesh[venue_key]
|
|
||||||
|
|
||||||
api_key_header: dict = {
|
|
||||||
# taken from official:
|
|
||||||
# https://github.com/binance/binance-futures-connector-python/blob/main/binance/api.py#L47
|
|
||||||
"Content-Type": "application/json;charset=utf-8",
|
|
||||||
|
|
||||||
# TODO: prolly should just always query and copy
|
|
||||||
# in the real latest ver?
|
|
||||||
"User-Agent": "binance-connector/6.1.6smbz6",
|
|
||||||
"X-MBX-APIKEY": api_key,
|
|
||||||
}
|
|
||||||
client.headers.update(api_key_header)
|
|
||||||
|
|
||||||
# if `.use_tesnet = true` in the config then
|
|
||||||
# also add headers for the testnet session which
|
|
||||||
# will be used for all order control
|
|
||||||
if subconf.get('use_testnet', False):
|
|
||||||
testnet_sesh, _ = client.venue_sesh[
|
|
||||||
venue_key + '_testnet'
|
|
||||||
]
|
|
||||||
testnet_sesh.headers.update(api_key_header)
|
|
||||||
|
|
||||||
|
|
||||||
@acm
|
@acm
|
||||||
async def get_client(
|
async def get_client() -> Client:
|
||||||
mkt_mode: MarketType = 'spot',
|
|
||||||
) -> Client:
|
|
||||||
'''
|
|
||||||
Construct an single `piker` client which composes multiple underlying venue
|
|
||||||
specific API clients both for live and test networks.
|
|
||||||
|
|
||||||
'''
|
client = Client()
|
||||||
venue_sessions: dict[
|
await client.exch_info()
|
||||||
str, # venue key
|
log.info(
|
||||||
tuple[httpx.AsyncClient, str] # session, eps path
|
f'{client} in {client.mkt_mode} mode: caching exchange infos..\n'
|
||||||
] = {}
|
'Cached multi-market pairs:\n'
|
||||||
async with AsyncExitStack() as client_stack:
|
f'spot: {len(client._spot_pairs)}\n'
|
||||||
for name, (base_url, path) in _venue_urls.items():
|
f'usdtm_futes: {len(client._ufutes_pairs)}\n'
|
||||||
api: httpx.AsyncClient = await client_stack.enter_async_context(
|
f'Total: {len(client._pairs)}\n'
|
||||||
httpx.AsyncClient(
|
)
|
||||||
base_url=base_url,
|
|
||||||
# headers={},
|
|
||||||
|
|
||||||
# TODO: is there a way to numerate this?
|
yield client
|
||||||
# https://www.python-httpx.org/advanced/clients/#why-use-a-client
|
|
||||||
# connections=4
|
|
||||||
)
|
|
||||||
)
|
|
||||||
venue_sessions[name] = (
|
|
||||||
api,
|
|
||||||
path,
|
|
||||||
)
|
|
||||||
|
|
||||||
conf: dict[str, Any] = get_config()
|
|
||||||
# for creating API keys see,
|
|
||||||
# https://www.binance.com/en/support/faq/how-to-create-api-keys-on-binance-360002502072
|
|
||||||
client = Client(
|
|
||||||
venue_sessions=venue_sessions,
|
|
||||||
conf=conf,
|
|
||||||
mkt_mode=mkt_mode,
|
|
||||||
)
|
|
||||||
init_api_keys(
|
|
||||||
client=client,
|
|
||||||
conf=conf,
|
|
||||||
)
|
|
||||||
fq_pairs: dict[str, Pair] = await client.exch_info()
|
|
||||||
assert fq_pairs
|
|
||||||
log.info(
|
|
||||||
f'Loaded multi-venue `Client` in mkt_mode={client.mkt_mode!r}\n\n'
|
|
||||||
f'Symbology Summary:\n'
|
|
||||||
f'------ - ------\n'
|
|
||||||
f'spot: {len(client._spot_pairs)}\n'
|
|
||||||
f'usdtm_futes: {len(client._ufutes_pairs)}\n'
|
|
||||||
'------ - ------\n'
|
|
||||||
f'total: {len(client._pairs)}\n'
|
|
||||||
)
|
|
||||||
yield client
|
|
||||||
|
|
|
@ -264,20 +264,15 @@ async def open_trade_dialog(
|
||||||
# do a open_symcache() call.. though maybe we can hide
|
# do a open_symcache() call.. though maybe we can hide
|
||||||
# this in a new async version of open_account()?
|
# this in a new async version of open_account()?
|
||||||
async with open_cached_client('binance') as client:
|
async with open_cached_client('binance') as client:
|
||||||
subconf: dict|None = client.conf.get(venue_name)
|
subconf: dict = client.conf[venue_name]
|
||||||
|
use_testnet = subconf.get('use_testnet', False)
|
||||||
|
|
||||||
# XXX: if no futes.api_key or spot.api_key has been set we
|
# XXX: if no futes.api_key or spot.api_key has been set we
|
||||||
# always fall back to the paper engine!
|
# always fall back to the paper engine!
|
||||||
if (
|
if not subconf.get('api_key'):
|
||||||
not subconf
|
|
||||||
or
|
|
||||||
not subconf.get('api_key')
|
|
||||||
):
|
|
||||||
await ctx.started('paper')
|
await ctx.started('paper')
|
||||||
return
|
return
|
||||||
|
|
||||||
use_testnet: bool = subconf.get('use_testnet', False)
|
|
||||||
|
|
||||||
async with (
|
async with (
|
||||||
open_cached_client('binance') as client,
|
open_cached_client('binance') as client,
|
||||||
):
|
):
|
||||||
|
|
|
@ -48,7 +48,6 @@ import tractor
|
||||||
|
|
||||||
from piker.brokers import (
|
from piker.brokers import (
|
||||||
open_cached_client,
|
open_cached_client,
|
||||||
NoData,
|
|
||||||
)
|
)
|
||||||
from piker._cacheables import (
|
from piker._cacheables import (
|
||||||
async_lifo_cache,
|
async_lifo_cache,
|
||||||
|
@ -253,30 +252,24 @@ async def open_history_client(
|
||||||
else:
|
else:
|
||||||
client.mkt_mode = 'spot'
|
client.mkt_mode = 'spot'
|
||||||
|
|
||||||
array: np.ndarray = await client.bars(
|
# NOTE: always query using their native symbology!
|
||||||
mkt=mkt,
|
mktid: str = mkt.bs_mktid
|
||||||
|
array = await client.bars(
|
||||||
|
mktid,
|
||||||
start_dt=start_dt,
|
start_dt=start_dt,
|
||||||
end_dt=end_dt,
|
end_dt=end_dt,
|
||||||
)
|
)
|
||||||
if array.size == 0:
|
|
||||||
raise NoData(
|
|
||||||
f'No frame for {start_dt} -> {end_dt}\n'
|
|
||||||
)
|
|
||||||
|
|
||||||
times = array['time']
|
times = array['time']
|
||||||
if not times.any():
|
if (
|
||||||
raise ValueError(
|
end_dt is None
|
||||||
'Bad frame with null-times?\n\n'
|
):
|
||||||
f'{times}'
|
inow = round(time.time())
|
||||||
)
|
|
||||||
|
|
||||||
if end_dt is None:
|
|
||||||
inow: int = round(time.time())
|
|
||||||
if (inow - times[-1]) > 60:
|
if (inow - times[-1]) > 60:
|
||||||
await tractor.pause()
|
await tractor.pause()
|
||||||
|
|
||||||
start_dt = from_timestamp(times[0])
|
start_dt = from_timestamp(times[0])
|
||||||
end_dt = from_timestamp(times[-1])
|
end_dt = from_timestamp(times[-1])
|
||||||
|
|
||||||
return array, start_dt, end_dt
|
return array, start_dt, end_dt
|
||||||
|
|
||||||
yield get_ohlc, {'erlangs': 3, 'rate': 3}
|
yield get_ohlc, {'erlangs': 3, 'rate': 3}
|
||||||
|
|
|
@ -137,12 +137,10 @@ class SpotPair(Pair, frozen=True):
|
||||||
quoteOrderQtyMarketAllowed: bool
|
quoteOrderQtyMarketAllowed: bool
|
||||||
isSpotTradingAllowed: bool
|
isSpotTradingAllowed: bool
|
||||||
isMarginTradingAllowed: bool
|
isMarginTradingAllowed: bool
|
||||||
otoAllowed: bool
|
|
||||||
|
|
||||||
defaultSelfTradePreventionMode: str
|
defaultSelfTradePreventionMode: str
|
||||||
allowedSelfTradePreventionModes: list[str]
|
allowedSelfTradePreventionModes: list[str]
|
||||||
permissions: list[str]
|
permissions: list[str]
|
||||||
permissionSets: list[list[str]]
|
|
||||||
|
|
||||||
# NOTE: see `.data._symcache.SymbologyCache.load()` for why
|
# NOTE: see `.data._symcache.SymbologyCache.load()` for why
|
||||||
ns_path: str = 'piker.brokers.binance:SpotPair'
|
ns_path: str = 'piker.brokers.binance:SpotPair'
|
||||||
|
|
Loading…
Reference in New Issue