Compare commits
	
		
			6 Commits 
		
	
	
		
			3e4e7c889b
			...
			bda7e69ad9
		
	
	| Author | SHA1 | Date | 
|---|---|---|
|  | bda7e69ad9 | |
|  | 0223074d24 | |
|  | 77d73d27db | |
|  | 411b517d8d | |
|  | 12250a7bb1 | |
|  | ddfffc38b0 | 
|  | @ -0,0 +1,82 @@ | |||
| with (import <nixpkgs> {}); | ||||
| with python312Packages; | ||||
| let | ||||
|   glibStorePath = lib.getLib glib; | ||||
|   qtpyStorePath = lib.getLib qtpy; | ||||
|   pyqt6StorePath = lib.getLib pyqt6; | ||||
|   pyqt6SipStorePath = lib.getLib pyqt6-sip; | ||||
|   qt6baseStorePath = lib.getLib qt6.qtbase; | ||||
|   rapidfuzzStorePath = lib.getLib rapidfuzz; | ||||
|   qdarkstyleStorePath = lib.getLib qdarkstyle; | ||||
| in | ||||
| stdenv.mkDerivation { | ||||
|   name = "piker-qt6-poetry-shell"; | ||||
|   buildInputs = [ | ||||
|     # System requirements. | ||||
|     glib | ||||
|     qt6.qtbase | ||||
|     libgcc.lib | ||||
| 
 | ||||
|     # Python requirements. | ||||
|     python312Full | ||||
|     poetry-core | ||||
|     qdarkstyle | ||||
|     rapidfuzz | ||||
|     pyqt6 | ||||
|     qtpy | ||||
|   ]; | ||||
|   src = null; | ||||
|   shellHook = '' | ||||
|     set -e | ||||
| 
 | ||||
|     export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:${libgcc.lib}/lib:${glibStorePath}/lib | ||||
| 
 | ||||
|     # Set the Qt plugin path | ||||
|     # export QT_DEBUG_PLUGINS=1 | ||||
| 
 | ||||
|     QTBASE_PATH="${qt6baseStorePath}" | ||||
|     echo "qtbase path:    $QTBASE_PATH" | ||||
|     echo "" | ||||
|     export QT_PLUGIN_PATH="$QTBASE_PATH/lib/qt-6/plugins" | ||||
|     export QT_QPA_PLATFORM_PLUGIN_PATH="$QT_PLUGIN_PATH/platforms" | ||||
|     echo "qt plugin path: $QT_PLUGIN_PATH" | ||||
|     echo "" | ||||
| 
 | ||||
|     # Maybe create venv & install deps | ||||
|     poetry install --with uis | ||||
| 
 | ||||
|     # Use pyqt6 from System, patch activate script | ||||
|     ACTIVATE_SCRIPT_PATH="$(poetry env info --path)/bin/activate" | ||||
| 
 | ||||
|     export RPDFUZZ_PATH="${rapidfuzzStorePath}/lib/python3.12/site-packages" | ||||
|     export QDRKSTYLE_PATH="${qdarkstyleStorePath}/lib/python3.12/site-packages" | ||||
|     export QTPY_PATH="${qtpyStorePath}/lib/python3.12/site-packages" | ||||
|     export PYQT6_PATH="${pyqt6StorePath}/lib/python3.12/site-packages" | ||||
|     export PYQT6_SIP_PATH="${pyqt6SipStorePath}/lib/python3.12/site-packages" | ||||
|     echo "rapidfuzz at:   $RPDFUZZ_PATH" | ||||
|     echo "qdarkstyle at:  $QDRKSTYLE_PATH" | ||||
|     echo "qtpy at:        $QTPY_PATH"  | ||||
|     echo "pyqt6 at:       $PYQT6_PATH" | ||||
|     echo "pyqt6-sip at:   $PYQT6_SIP_PATH" | ||||
|     echo "" | ||||
| 
 | ||||
|     PATCH="export PYTHONPATH=\"" | ||||
| 
 | ||||
|     PATCH="$PATCH\$RPDFUZZ_PATH" | ||||
|     PATCH="$PATCH:\$QDRKSTYLE_PATH" | ||||
|     PATCH="$PATCH:\$QTPY_PATH" | ||||
|     PATCH="$PATCH:\$PYQT6_PATH" | ||||
|     PATCH="$PATCH:\$PYQT6_SIP_PATH" | ||||
| 
 | ||||
|     PATCH="$PATCH\"" | ||||
| 
 | ||||
|     if grep -q "$PATCH" "$ACTIVATE_SCRIPT_PATH"; then | ||||
|         echo "venv is already patched." | ||||
|     else | ||||
|         echo "patching $ACTIVATE_SCRIPT_PATH to use pyqt6 from nixos..." | ||||
|         sed -i "\$i$PATCH" $ACTIVATE_SCRIPT_PATH | ||||
|     fi | ||||
| 
 | ||||
|     poetry shell | ||||
|   ''; | ||||
| } | ||||
|  | @ -30,7 +30,8 @@ from types import ModuleType | |||
| from typing import ( | ||||
|     Any, | ||||
|     Iterator, | ||||
|     Generator | ||||
|     Generator, | ||||
|     TYPE_CHECKING, | ||||
| ) | ||||
| 
 | ||||
| import pendulum | ||||
|  | @ -59,8 +60,10 @@ from ..clearing._messages import ( | |||
|     BrokerdPosition, | ||||
| ) | ||||
| from piker.types import Struct | ||||
| from piker.data._symcache import SymbologyCache | ||||
| from ..log import get_logger | ||||
| from piker.log import get_logger | ||||
| 
 | ||||
| if TYPE_CHECKING: | ||||
|     from piker.data._symcache import SymbologyCache | ||||
| 
 | ||||
| log = get_logger(__name__) | ||||
| 
 | ||||
|  | @ -493,6 +496,17 @@ class Account(Struct): | |||
| 
 | ||||
|         _mktmap_table: dict[str, MktPair] | None = None, | ||||
| 
 | ||||
|         only_require: list[str]|True = True, | ||||
|         # ^list of fqmes that are "required" to be processed from | ||||
|         # this ledger pass; we often don't care about others and | ||||
|         # definitely shouldn't always error in such cases. | ||||
|         # (eg. broker backend loaded that doesn't yet supsport the | ||||
|         # symcache but also, inside the paper engine we don't ad-hoc | ||||
|         # request `get_mkt_info()` for every symbol in the ledger, | ||||
|         # only the one for which we're simulating against). | ||||
|         # TODO, not sure if there's a better soln for this, ideally | ||||
|         # all backends get symcache support afap i guess.. | ||||
| 
 | ||||
|     ) -> dict[str, Position]: | ||||
|         ''' | ||||
|         Update the internal `.pps[str, Position]` table from input | ||||
|  | @ -535,11 +549,32 @@ class Account(Struct): | |||
|                 if _mktmap_table is None: | ||||
|                     raise | ||||
| 
 | ||||
|                 required: bool = ( | ||||
|                     only_require is True | ||||
|                     or ( | ||||
|                         only_require is not True | ||||
|                         and | ||||
|                         fqme in only_require | ||||
|                     ) | ||||
|                 ) | ||||
|                 # XXX: caller is allowed to provide a fallback | ||||
|                 # mktmap table for the case where a new position is | ||||
|                 # being added and the preloaded symcache didn't | ||||
|                 # have this entry prior (eg. with frickin IB..) | ||||
|                 mkt = _mktmap_table[fqme] | ||||
|                 if ( | ||||
|                     not (mkt := _mktmap_table.get(fqme)) | ||||
|                     and | ||||
|                     required | ||||
|                 ): | ||||
|                     raise | ||||
| 
 | ||||
|                 elif not required: | ||||
|                     continue | ||||
| 
 | ||||
|                 else: | ||||
|                     # should be an entry retreived somewhere | ||||
|                     assert mkt | ||||
| 
 | ||||
| 
 | ||||
|             if not (pos := pps.get(bs_mktid)): | ||||
| 
 | ||||
|  | @ -656,7 +691,7 @@ class Account(Struct): | |||
|     def write_config(self) -> None: | ||||
|         ''' | ||||
|         Write the current account state to the user's account TOML file, normally | ||||
|         something like ``pps.toml``. | ||||
|         something like `pps.toml`. | ||||
| 
 | ||||
|         ''' | ||||
|         # TODO: show diff output? | ||||
|  |  | |||
|  | @ -181,7 +181,6 @@ class FutesPair(Pair): | |||
|     quoteAsset: str  # 'USDT', | ||||
|     quotePrecision: int  # 8, | ||||
|     requiredMarginPercent: float  # '5.0000', | ||||
|     settlePlan: int  # 0, | ||||
|     timeInForce: list[str]  # ['GTC', 'IOC', 'FOK', 'GTX'], | ||||
|     triggerProtect: float  # '0.0500', | ||||
|     underlyingSubType: list[str]  # ['PoW'], | ||||
|  |  | |||
|  | @ -111,6 +111,10 @@ class KucoinMktPair(Struct, frozen=True): | |||
|     quoteMaxSize: float | ||||
|     quoteMinSize: float | ||||
|     symbol: str  # our bs_mktid, kucoin's internal id | ||||
|     feeCategory: int | ||||
|     makerFeeCoefficient: float | ||||
|     takerFeeCoefficient: float | ||||
|     st: bool | ||||
| 
 | ||||
| 
 | ||||
| class AccountTrade(Struct, frozen=True): | ||||
|  | @ -593,7 +597,7 @@ async def get_client() -> AsyncGenerator[Client, None]: | |||
|     ''' | ||||
|     async with ( | ||||
|         httpx.AsyncClient( | ||||
|             base_url=f'https://api.kucoin.com/api', | ||||
|             base_url='https://api.kucoin.com/api', | ||||
|         ) as trio_client, | ||||
|     ): | ||||
|         client = Client(httpx_client=trio_client) | ||||
|  | @ -637,7 +641,7 @@ async def open_ping_task( | |||
|                 await trio.sleep((ping_interval - 1000) / 1000) | ||||
|                 await ws.send_msg({'id': connect_id, 'type': 'ping'}) | ||||
| 
 | ||||
|         log.info('Starting ping task for kucoin ws connection') | ||||
|         log.warning('Starting ping task for kucoin ws connection') | ||||
|         n.start_soon(ping_server) | ||||
| 
 | ||||
|         yield | ||||
|  | @ -649,9 +653,14 @@ async def open_ping_task( | |||
| async def get_mkt_info( | ||||
|     fqme: str, | ||||
| 
 | ||||
| ) -> tuple[MktPair, KucoinMktPair]: | ||||
| ) -> tuple[ | ||||
|     MktPair, | ||||
|     KucoinMktPair, | ||||
| ]: | ||||
|     ''' | ||||
|     Query for and return a `MktPair` and `KucoinMktPair`. | ||||
|     Query for and return both a `piker.accounting.MktPair` and | ||||
|     `KucoinMktPair` from provided `fqme: str` | ||||
|     (fully-qualified-market-endpoint). | ||||
| 
 | ||||
|     ''' | ||||
|     async with open_cached_client('kucoin') as client: | ||||
|  | @ -726,6 +735,8 @@ async def stream_quotes( | |||
| 
 | ||||
|         log.info(f'Starting up quote stream(s) for {symbols}') | ||||
|         for sym_str in symbols: | ||||
|             mkt: MktPair | ||||
|             pair: KucoinMktPair | ||||
|             mkt, pair = await get_mkt_info(sym_str) | ||||
|             init_msgs.append( | ||||
|                 FeedInit(mkt_info=mkt) | ||||
|  | @ -733,7 +744,11 @@ async def stream_quotes( | |||
| 
 | ||||
|         ws: NoBsWs | ||||
|         token, ping_interval = await client._get_ws_token() | ||||
|         connect_id = str(uuid4()) | ||||
|         log.info('API reported ping_interval: {ping_interval}\n') | ||||
| 
 | ||||
|         connect_id: str = str(uuid4()) | ||||
|         typ: str | ||||
|         quote: dict | ||||
|         async with ( | ||||
|             open_autorecon_ws( | ||||
|                 ( | ||||
|  | @ -747,20 +762,37 @@ async def stream_quotes( | |||
|                 ), | ||||
|             ) as ws, | ||||
|             open_ping_task(ws, ping_interval, connect_id), | ||||
|             aclosing(stream_messages(ws, sym_str)) as msg_gen, | ||||
|             aclosing( | ||||
|                 iter_normed_quotes( | ||||
|                     ws, sym_str | ||||
|                 ) | ||||
|             ) as iter_quotes, | ||||
|         ): | ||||
|             typ, quote = await anext(msg_gen) | ||||
|             typ, quote = await anext(iter_quotes) | ||||
| 
 | ||||
|             while typ != 'trade': | ||||
|                 # take care to not unblock here until we get a real | ||||
|                 # trade quote | ||||
|                 typ, quote = await anext(msg_gen) | ||||
|             # take care to not unblock here until we get a real | ||||
|             # trade quote? | ||||
|             # ^TODO, remove this right? | ||||
|             # -[ ] what often blocks chart boot/new-feed switching | ||||
|             #   since we'ere waiting for a live quote instead of just | ||||
|             #   loading history afap.. | ||||
|             #  |_ XXX, not sure if we require a bit of rework to core | ||||
|             #    feed init logic or if backends justg gotta be | ||||
|             #    changed up.. feel like there was some causality | ||||
|             #    dilema prolly only seen with IB too.. | ||||
|             # while typ != 'trade': | ||||
|             #     typ, quote = await anext(iter_quotes) | ||||
| 
 | ||||
|             task_status.started((init_msgs, quote)) | ||||
|             feed_is_live.set() | ||||
| 
 | ||||
|             async for typ, msg in msg_gen: | ||||
|                 await send_chan.send({sym_str: msg}) | ||||
|             # XXX NOTE, DO NOT include the `.<backend>` suffix! | ||||
|             # OW the sampling loop will not broadcast correctly.. | ||||
|             # since `bus._subscribers.setdefault(bs_fqme, set())` | ||||
|             # is used inside `.data.open_feed_bus()` !!! | ||||
|             topic: str = mkt.bs_fqme | ||||
|             async for typ, quote in iter_quotes: | ||||
|                 await send_chan.send({topic: quote}) | ||||
| 
 | ||||
| 
 | ||||
| @acm | ||||
|  | @ -815,7 +847,7 @@ async def subscribe( | |||
|             ) | ||||
| 
 | ||||
| 
 | ||||
| async def stream_messages( | ||||
| async def iter_normed_quotes( | ||||
|     ws: NoBsWs, | ||||
|     sym: str, | ||||
| 
 | ||||
|  | @ -846,6 +878,9 @@ async def stream_messages( | |||
| 
 | ||||
|                 yield 'trade', { | ||||
|                     'symbol': sym, | ||||
|                     # TODO, is 'last' even used elsewhere/a-good | ||||
|                     # semantic? can't we just read the ticks with our | ||||
|                     # .data.ticktools.frame_ticks()`/ | ||||
|                     'last': trade_data.price, | ||||
|                     'brokerd_ts': last_trade_ts, | ||||
|                     'ticks': [ | ||||
|  | @ -938,7 +973,7 @@ async def open_history_client( | |||
|             if end_dt is None: | ||||
|                 inow = round(time.time()) | ||||
| 
 | ||||
|                 print( | ||||
|                 log.debug( | ||||
|                     f'difference in time between load and processing' | ||||
|                     f'{inow - times[-1]}' | ||||
|                 ) | ||||
|  |  | |||
|  | @ -653,7 +653,11 @@ class Router(Struct): | |||
|             flume = feed.flumes[fqme] | ||||
|             first_quote: dict = flume.first_quote | ||||
|             book: DarkBook = self.get_dark_book(broker) | ||||
|             book.lasts[fqme]: float = float(first_quote['last']) | ||||
| 
 | ||||
|             if not (last := first_quote.get('last')): | ||||
|                 last: float = flume.rt_shm.array[-1]['close'] | ||||
| 
 | ||||
|             book.lasts[fqme]: float = float(last) | ||||
| 
 | ||||
|             async with self.maybe_open_brokerd_dialog( | ||||
|                 brokermod=brokermod, | ||||
|  | @ -716,7 +720,7 @@ class Router(Struct): | |||
|             subs = self.subscribers[sub_key] | ||||
| 
 | ||||
|         sent_some: bool = False | ||||
|         for client_stream in subs: | ||||
|         for client_stream in subs.copy(): | ||||
|             try: | ||||
|                 await client_stream.send(msg) | ||||
|                 sent_some = True | ||||
|  | @ -1010,10 +1014,14 @@ async def translate_and_relay_brokerd_events( | |||
|                 status_msg.brokerd_msg = msg | ||||
|                 status_msg.src = msg.broker_details['name'] | ||||
| 
 | ||||
|                 await router.client_broadcast( | ||||
|                     status_msg.req.symbol, | ||||
|                     status_msg, | ||||
|                 ) | ||||
|                 if not status_msg.req: | ||||
|                     # likely some order change state? | ||||
|                     await tractor.pause() | ||||
|                 else: | ||||
|                     await router.client_broadcast( | ||||
|                         status_msg.req.symbol, | ||||
|                         status_msg, | ||||
|                     ) | ||||
| 
 | ||||
|                 if status == 'closed': | ||||
|                     log.info(f'Execution for {oid} is complete!') | ||||
|  |  | |||
|  | @ -653,6 +653,7 @@ async def open_trade_dialog( | |||
|                 # in) use manually constructed table from calling | ||||
|                 # the `.get_mkt_info()` provider EP above. | ||||
|                 _mktmap_table=mkt_by_fqme, | ||||
|                 only_require=list(mkt_by_fqme), | ||||
|             ) | ||||
| 
 | ||||
|             pp_msgs: list[BrokerdPosition] = [] | ||||
|  |  | |||
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							|  | @ -50,10 +50,8 @@ attrs = "^23.1.0" | |||
| bidict = "^0.22.1" | ||||
| colorama = "^0.4.6" | ||||
| colorlog = "^6.7.0" | ||||
| cython = "^3.0.0" | ||||
| greenback = "^1.1.1" | ||||
| ib-insync = "^0.9.86" | ||||
| msgspec = "^0.18.0" | ||||
| msgspec = "^0.18.6" | ||||
| numba = "^0.59.0" | ||||
| numpy = "^1.25" | ||||
| polars = "^0.18.13" | ||||
|  | @ -74,8 +72,8 @@ httpx = "^0.27.0" | |||
| 
 | ||||
| [tool.poetry.dependencies.tractor] | ||||
| develop = true | ||||
| git = 'https://github.com/goodboy/tractor.git' | ||||
| branch = 'asyncio_debugger_support' | ||||
| git = 'https://pikers.dev/goodboy/tractor.git' | ||||
| branch = 'aio_abandons' | ||||
| # path = "../tractor" | ||||
| 
 | ||||
| [tool.poetry.dependencies.asyncvnc] | ||||
|  | @ -109,6 +107,8 @@ pytest = "^6.0.0" | |||
| elasticsearch = "^8.9.0" | ||||
| xonsh = "^0.14.2" | ||||
| prompt-toolkit = "3.0.40" | ||||
| cython = "^3.0.0" | ||||
| greenback = "^1.1.1" | ||||
| 
 | ||||
| # console ehancements and eventually remote debugging | ||||
| # extras/helpers. | ||||
|  |  | |||
		Loading…
	
		Reference in New Issue