Factor `Pair` schema-mismatch handling to `_util`
Add `get_or_raise_on_pair_schema_mismatch()` helper and `SchemaMismatch` error type in `brokers._util` to standardize the "provider changed their API" error reporting across backends. Deats, - add `SchemaMismatch(BrokerError)` exc type. - `get_or_raise_on_pair_schema_mismatch()`: catch `TypeError` on `Pair` ctor, build `ppfmt()`-ed report with provider name, fall back to `pair_type._api_url` if no explicit URL passed, then raise `SchemaMismatch`. - binance `api.py`: replace inline `try/except` + `e.add_note()` with the new helper. - kraken `api.py`: replace bare `Pair(...)` ctor with the new helper inside crash handler. Also, - add `_api_url: ClassVar[str]` to binance `FutesPair` and kraken `Pair` structs. - binance `feed.py`: warn on missing `.<provider>` in fqme; raise `SymbolNotFound` on empty venue. - reformat `start_dt`/`end_dt` unions to `datetime|None` style in binance `Client`. - wrap binance `_pairs` lookup in `maybe_open_crash_handler()`. (this commit msg was generated in some part by [`claude-code`][claude-code-gh]) [claude-code-gh]: https://github.com/anthropics/claude-coderepair_tests
parent
60b4526fad
commit
b30372f4fe
|
|
@ -20,10 +20,14 @@ Handy cross-broker utils.
|
||||||
"""
|
"""
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
# from functools import partial
|
# from functools import partial
|
||||||
|
from typing import (
|
||||||
|
Type,
|
||||||
|
)
|
||||||
|
|
||||||
import json
|
import json
|
||||||
import httpx
|
import httpx
|
||||||
import logging
|
import logging
|
||||||
|
from msgspec import Struct
|
||||||
|
|
||||||
from piker.log import (
|
from piker.log import (
|
||||||
colorize_json,
|
colorize_json,
|
||||||
|
|
@ -97,6 +101,12 @@ class DataThrottle(BrokerError):
|
||||||
'''
|
'''
|
||||||
# TODO: add in throttle metrics/feedback
|
# TODO: add in throttle metrics/feedback
|
||||||
|
|
||||||
|
class SchemaMismatch(BrokerError):
|
||||||
|
'''
|
||||||
|
Market `Pair` fields mismatch, likely due to provider API update.
|
||||||
|
|
||||||
|
'''
|
||||||
|
|
||||||
|
|
||||||
def resproc(
|
def resproc(
|
||||||
resp: httpx.Response,
|
resp: httpx.Response,
|
||||||
|
|
@ -123,3 +133,45 @@ def resproc(
|
||||||
log.debug(f"Received json contents:\n{colorize_json(msg)}")
|
log.debug(f"Received json contents:\n{colorize_json(msg)}")
|
||||||
|
|
||||||
return msg if return_json else resp
|
return msg if return_json else resp
|
||||||
|
|
||||||
|
|
||||||
|
def get_or_raise_on_pair_schema_mismatch(
|
||||||
|
pair_type: Type[Struct],
|
||||||
|
fields_data: dict,
|
||||||
|
provider_name: str,
|
||||||
|
api_url: str|None = None,
|
||||||
|
) -> Struct:
|
||||||
|
'''
|
||||||
|
Boilerplate helper around assset-`Pair` field schema mismatches,
|
||||||
|
normally due to provider API updates.
|
||||||
|
|
||||||
|
'''
|
||||||
|
try:
|
||||||
|
pair: Struct = pair_type(**fields_data)
|
||||||
|
return pair
|
||||||
|
except TypeError as err:
|
||||||
|
|
||||||
|
from tractor.devx.pformat import ppfmt
|
||||||
|
repr_data: str = ppfmt(fields_data)
|
||||||
|
report: str = (
|
||||||
|
f'Field mismatch we need to codify!\n'
|
||||||
|
f'\n'
|
||||||
|
f'{pair_type!r}({repr_data})'
|
||||||
|
f'\n'
|
||||||
|
f'^^^ {err.args[0]!r} ^^^\n'
|
||||||
|
f'\n'
|
||||||
|
f"Don't panic, prolly {provider_name!r} "
|
||||||
|
f"changed their symbology schema..\n"
|
||||||
|
)
|
||||||
|
if (
|
||||||
|
api_url
|
||||||
|
or
|
||||||
|
(api_url := pair_type._api_url)
|
||||||
|
):
|
||||||
|
report += (
|
||||||
|
f'\n'
|
||||||
|
f'Check out their API docs here:\n'
|
||||||
|
f'{api_url}\n'
|
||||||
|
)
|
||||||
|
|
||||||
|
raise SchemaMismatch(report) from err
|
||||||
|
|
|
||||||
|
|
@ -49,6 +49,9 @@ from piker import config
|
||||||
from piker.clearing._messages import (
|
from piker.clearing._messages import (
|
||||||
Order,
|
Order,
|
||||||
)
|
)
|
||||||
|
from piker.brokers._util import (
|
||||||
|
get_or_raise_on_pair_schema_mismatch,
|
||||||
|
)
|
||||||
from piker.accounting import (
|
from piker.accounting import (
|
||||||
Asset,
|
Asset,
|
||||||
digits_to_dec,
|
digits_to_dec,
|
||||||
|
|
@ -370,20 +373,12 @@ class Client:
|
||||||
item['filters'] = filters
|
item['filters'] = filters
|
||||||
|
|
||||||
pair_type: Type = PAIRTYPES[venue]
|
pair_type: Type = PAIRTYPES[venue]
|
||||||
try:
|
pair: Pair = get_or_raise_on_pair_schema_mismatch(
|
||||||
pair: Pair = pair_type(**item)
|
pair_type=pair_type,
|
||||||
except Exception as e:
|
fields_data=item,
|
||||||
e.add_note(
|
provider_name='binance',
|
||||||
f'\n'
|
api_url='https://binance-docs.github.io/apidocs/spot/en/#exchange-information',
|
||||||
f'New or removed field we need to codify!\n'
|
)
|
||||||
f'pair-type: {pair_type!r}\n'
|
|
||||||
f'\n'
|
|
||||||
f"Don't panic, prolly stupid binance changed their symbology schema again..\n"
|
|
||||||
f'Check out their API docs here:\n'
|
|
||||||
f'\n'
|
|
||||||
f'https://binance-docs.github.io/apidocs/spot/en/#exchange-information\n'
|
|
||||||
)
|
|
||||||
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
|
||||||
|
|
@ -581,8 +576,8 @@ class Client:
|
||||||
self,
|
self,
|
||||||
mkt: MktPair,
|
mkt: MktPair,
|
||||||
|
|
||||||
start_dt: datetime | None = None,
|
start_dt: datetime|None = None,
|
||||||
end_dt: datetime | None = None,
|
end_dt: datetime|None = None,
|
||||||
|
|
||||||
as_np: bool = True,
|
as_np: bool = True,
|
||||||
|
|
||||||
|
|
@ -609,7 +604,11 @@ 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()]
|
import tractor
|
||||||
|
with tractor.devx.maybe_open_crash_handler():
|
||||||
|
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(
|
||||||
|
|
|
||||||
|
|
@ -48,6 +48,7 @@ import tractor
|
||||||
from piker.brokers import (
|
from piker.brokers import (
|
||||||
open_cached_client,
|
open_cached_client,
|
||||||
NoData,
|
NoData,
|
||||||
|
SymbolNotFound,
|
||||||
)
|
)
|
||||||
from piker._cacheables import (
|
from piker._cacheables import (
|
||||||
async_lifo_cache,
|
async_lifo_cache,
|
||||||
|
|
@ -305,6 +306,10 @@ async def get_mkt_info(
|
||||||
|
|
||||||
# uppercase since kraken bs_mktid is always upper
|
# uppercase since kraken bs_mktid is always upper
|
||||||
if 'binance' not in fqme.lower():
|
if 'binance' not in fqme.lower():
|
||||||
|
log.warning(
|
||||||
|
f'Missing `.<provider>` part in fqme ??\n'
|
||||||
|
f'fqme: {fqme!r}\n'
|
||||||
|
)
|
||||||
fqme += '.binance'
|
fqme += '.binance'
|
||||||
|
|
||||||
mkt_mode: str = ''
|
mkt_mode: str = ''
|
||||||
|
|
@ -319,6 +324,12 @@ async def get_mkt_info(
|
||||||
venue: str = venue.upper()
|
venue: str = venue.upper()
|
||||||
venue_lower: str = venue.lower()
|
venue_lower: str = venue.lower()
|
||||||
|
|
||||||
|
if not venue:
|
||||||
|
raise SymbolNotFound(
|
||||||
|
f'Invalid or missing .<venue> part in fqme?\n'
|
||||||
|
f'fqme: {fqme!r}\n'
|
||||||
|
)
|
||||||
|
|
||||||
# XXX TODO: we should change the usdtm_futes name to just
|
# XXX TODO: we should change the usdtm_futes name to just
|
||||||
# usdm_futes (dropping the tether part) since it turns out that
|
# usdm_futes (dropping the tether part) since it turns out that
|
||||||
# there are indeed USD-tokens OTHER THEN tether being used as
|
# there are indeed USD-tokens OTHER THEN tether being used as
|
||||||
|
|
@ -360,6 +371,8 @@ async def get_mkt_info(
|
||||||
if not mkt_mode:
|
if not mkt_mode:
|
||||||
mkt_mode: str = f'{venue_lower}_futes'
|
mkt_mode: str = f'{venue_lower}_futes'
|
||||||
|
|
||||||
|
await tractor.pause()
|
||||||
|
|
||||||
async with open_cached_client(
|
async with open_cached_client(
|
||||||
'binance',
|
'binance',
|
||||||
) as client:
|
) as client:
|
||||||
|
|
|
||||||
|
|
@ -20,6 +20,7 @@ Per market data-type definitions and schemas types.
|
||||||
"""
|
"""
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
from typing import (
|
from typing import (
|
||||||
|
ClassVar,
|
||||||
Literal,
|
Literal,
|
||||||
)
|
)
|
||||||
from decimal import Decimal
|
from decimal import Decimal
|
||||||
|
|
@ -203,6 +204,8 @@ class FutesPair(Pair):
|
||||||
# NOTE: see `.data._symcache.SymbologyCache.load()` for why
|
# NOTE: see `.data._symcache.SymbologyCache.load()` for why
|
||||||
ns_path: str = 'piker.brokers.binance:FutesPair'
|
ns_path: str = 'piker.brokers.binance:FutesPair'
|
||||||
|
|
||||||
|
_api_url: ClassVar[str] = 'https://binance-docs.github.io/apidocs/spot/en/#exchange-information'
|
||||||
|
|
||||||
# NOTE: for compat with spot pairs and `MktPair.src: Asset`
|
# NOTE: for compat with spot pairs and `MktPair.src: Asset`
|
||||||
# processing..
|
# processing..
|
||||||
@property
|
@property
|
||||||
|
|
|
||||||
|
|
@ -52,6 +52,7 @@ from piker.brokers._util import (
|
||||||
SymbolNotFound,
|
SymbolNotFound,
|
||||||
BrokerError,
|
BrokerError,
|
||||||
DataThrottle,
|
DataThrottle,
|
||||||
|
get_or_raise_on_pair_schema_mismatch,
|
||||||
)
|
)
|
||||||
from piker.accounting import Transaction
|
from piker.accounting import Transaction
|
||||||
from piker.log import get_logger
|
from piker.log import get_logger
|
||||||
|
|
@ -502,7 +503,16 @@ class Client:
|
||||||
|
|
||||||
# NOTE: always cache in pairs tables for faster lookup
|
# NOTE: always cache in pairs tables for faster lookup
|
||||||
with tractor.devx.maybe_open_crash_handler(): # as bxerr:
|
with tractor.devx.maybe_open_crash_handler(): # as bxerr:
|
||||||
pair = Pair(xname=xkey, **data)
|
# pair = Pair(xname=xkey, **data)
|
||||||
|
pair: Pair = get_or_raise_on_pair_schema_mismatch(
|
||||||
|
pair_type=Pair,
|
||||||
|
fields_data=dict(
|
||||||
|
xname=xkey,
|
||||||
|
**data,
|
||||||
|
),
|
||||||
|
provider_name='kraken',
|
||||||
|
# api_url='https://binance-docs.github.io/apidocs/spot/en/#exchange-information',
|
||||||
|
)
|
||||||
|
|
||||||
# register the above `Pair` structs for all
|
# register the above `Pair` structs for all
|
||||||
# key-sets/monikers: a set of 4 (frickin) tables
|
# key-sets/monikers: a set of 4 (frickin) tables
|
||||||
|
|
|
||||||
|
|
@ -19,6 +19,9 @@ Symbology defs and search.
|
||||||
|
|
||||||
'''
|
'''
|
||||||
from decimal import Decimal
|
from decimal import Decimal
|
||||||
|
from typing import (
|
||||||
|
ClassVar,
|
||||||
|
)
|
||||||
|
|
||||||
import tractor
|
import tractor
|
||||||
|
|
||||||
|
|
@ -86,9 +89,14 @@ class Pair(Struct):
|
||||||
short_position_limit: float = 0
|
short_position_limit: float = 0
|
||||||
long_position_limit: float = float('inf')
|
long_position_limit: float = float('inf')
|
||||||
|
|
||||||
|
# TODO, add API note when this was added!
|
||||||
|
# execution_venue: str|None = None
|
||||||
|
|
||||||
# TODO: should we make this a literal NamespacePath ref?
|
# TODO: should we make this a literal NamespacePath ref?
|
||||||
ns_path: str = 'piker.brokers.kraken:Pair'
|
ns_path: str = 'piker.brokers.kraken:Pair'
|
||||||
|
|
||||||
|
_api_url: ClassVar[str] = 'https://docs.kraken.com/api/docs/rest-api/get-tradable-asset-pairs'
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def bs_mktid(self) -> str:
|
def bs_mktid(self) -> str:
|
||||||
'''
|
'''
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue