Compare commits
	
		
			4 Commits 
		
	
	
		
			01a9dc3f4f
			...
			3fc9c1486a
		
	
	| Author | SHA1 | Date | 
|---|---|---|
|  | 3fc9c1486a | |
|  | 6e1cb5f147 | |
|  | dc2b255548 | |
|  | 2ef20761a0 | 
							
								
								
									
										148
									
								
								default.nix
								
								
								
								
							
							
						
						
									
										148
									
								
								default.nix
								
								
								
								
							|  | @ -1,82 +1,130 @@ | ||||||
| with (import <nixpkgs> {}); | with (import <nixpkgs> {}); | ||||||
| with python312Packages; |  | ||||||
| let | let | ||||||
|   glibStorePath = lib.getLib glib; |   glibStorePath = lib.getLib glib; | ||||||
|   qtpyStorePath = lib.getLib qtpy; |   zstdStorePath = lib.getLib zstd; | ||||||
|   pyqt6StorePath = lib.getLib pyqt6; |   dbusStorePath = lib.getLib dbus; | ||||||
|   pyqt6SipStorePath = lib.getLib pyqt6-sip; |   libGLStorePath = lib.getLib libGL; | ||||||
|  |   freetypeStorePath = lib.getLib freetype; | ||||||
|   qt6baseStorePath = lib.getLib qt6.qtbase; |   qt6baseStorePath = lib.getLib qt6.qtbase; | ||||||
|   rapidfuzzStorePath = lib.getLib rapidfuzz; |   fontconfigStorePath = lib.getLib fontconfig; | ||||||
|   qdarkstyleStorePath = lib.getLib qdarkstyle; |   libxkbcommonStorePath = lib.getLib libxkbcommon; | ||||||
|  |   xcbutilcursorStorePath = lib.getLib xcb-util-cursor; | ||||||
|  | 
 | ||||||
|  |   qtpyStorePath = lib.getLib python312Packages.qtpy; | ||||||
|  |   pyqt6StorePath = lib.getLib python312Packages.pyqt6; | ||||||
|  |   pyqt6SipStorePath = lib.getLib python312Packages.pyqt6-sip; | ||||||
|  |   rapidfuzzStorePath = lib.getLib python312Packages.rapidfuzz; | ||||||
|  |   qdarkstyleStorePath = lib.getLib python312Packages.qdarkstyle; | ||||||
|  | 
 | ||||||
|  |   xorgLibX11StorePath = lib.getLib xorg.libX11; | ||||||
|  |   xorgLibxcbStorePath = lib.getLib xorg.libxcb; | ||||||
|  |   xorgxcbutilwmStorePath = lib.getLib xorg.xcbutilwm; | ||||||
|  |   xorgxcbutilimageStorePath = lib.getLib xorg.xcbutilimage; | ||||||
|  |   xorgxcbutilerrorsStorePath = lib.getLib xorg.xcbutilerrors; | ||||||
|  |   xorgxcbutilkeysymsStorePath = lib.getLib xorg.xcbutilkeysyms; | ||||||
|  |   xorgxcbutilrenderutilStorePath = lib.getLib xorg.xcbutilrenderutil; | ||||||
| in | in | ||||||
| stdenv.mkDerivation { | stdenv.mkDerivation { | ||||||
|   name = "piker-qt6-poetry-shell"; |   name = "piker-qt6-uv"; | ||||||
|   buildInputs = [ |   buildInputs = [ | ||||||
|     # System requirements. |     # System requirements. | ||||||
|     glib |     glib | ||||||
|  |     dbus | ||||||
|  |     zstd | ||||||
|  |     libGL | ||||||
|  |     freetype | ||||||
|     qt6.qtbase |     qt6.qtbase | ||||||
|     libgcc.lib |     libgcc.lib | ||||||
|  |     fontconfig | ||||||
|  |     libxkbcommon | ||||||
|  | 
 | ||||||
|  |     # Xorg requirements | ||||||
|  |     xcb-util-cursor | ||||||
|  |     xorg.libxcb | ||||||
|  |     xorg.libX11 | ||||||
|  |     xorg.xcbutilwm | ||||||
|  |     xorg.xcbutilimage | ||||||
|  |     xorg.xcbutilerrors | ||||||
|  |     xorg.xcbutilkeysyms | ||||||
|  |     xorg.xcbutilrenderutil | ||||||
| 
 | 
 | ||||||
|     # Python requirements. |     # Python requirements. | ||||||
|     python312Full |     python312Full | ||||||
|     poetry-core |     python312Packages.uv | ||||||
|     qdarkstyle |     python312Packages.qdarkstyle | ||||||
|     rapidfuzz |     python312Packages.rapidfuzz | ||||||
|     pyqt6 |     python312Packages.pyqt6 | ||||||
|     qtpy |     python312Packages.qtpy | ||||||
|   ]; |   ]; | ||||||
|   src = null; |   src = null; | ||||||
|   shellHook = '' |   shellHook = '' | ||||||
|     set -e |     set -e | ||||||
| 
 | 
 | ||||||
|     export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:${libgcc.lib}/lib:${glibStorePath}/lib |  | ||||||
| 
 |  | ||||||
|     # Set the Qt plugin path |     # Set the Qt plugin path | ||||||
|     # export QT_DEBUG_PLUGINS=1 |     # export QT_DEBUG_PLUGINS=1 | ||||||
|      |      | ||||||
|     QTBASE_PATH="${qt6baseStorePath}" |     QTBASE_PATH="${qt6baseStorePath}/lib" | ||||||
|     echo "qtbase path:    $QTBASE_PATH" |     QT_PLUGIN_PATH="$QTBASE_PATH/qt-6/plugins" | ||||||
|     echo "" |     QT_QPA_PLATFORM_PLUGIN_PATH="$QT_PLUGIN_PATH/platforms" | ||||||
|     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 |     LIB_GCC_PATH="${libgcc.lib}/lib" | ||||||
|     poetry install --with uis |     GLIB_PATH="${glibStorePath}/lib" | ||||||
|  |     ZSTD_PATH="${zstdStorePath}/lib" | ||||||
|  |     DBUS_PATH="${dbusStorePath}/lib" | ||||||
|  |     LIBGL_PATH="${libGLStorePath}/lib" | ||||||
|  |     FREETYPE_PATH="${freetypeStorePath}/lib" | ||||||
|  |     FONTCONFIG_PATH="${fontconfigStorePath}/lib" | ||||||
|  |     LIB_XKB_COMMON_PATH="${libxkbcommonStorePath}/lib" | ||||||
| 
 | 
 | ||||||
|     # Use pyqt6 from System, patch activate script |     XCB_UTIL_CURSOR_PATH="${xcbutilcursorStorePath}/lib" | ||||||
|     ACTIVATE_SCRIPT_PATH="$(poetry env info --path)/bin/activate" |     XORG_LIB_X11_PATH="${xorgLibX11StorePath}/lib" | ||||||
|  |     XORG_LIB_XCB_PATH="${xorgLibxcbStorePath}/lib" | ||||||
|  |     XORG_XCB_UTIL_IMAGE_PATH="${xorgxcbutilimageStorePath}/lib" | ||||||
|  |     XORG_XCB_UTIL_WM_PATH="${xorgxcbutilwmStorePath}/lib" | ||||||
|  |     XORG_XCB_UTIL_RENDER_UTIL_PATH="${xorgxcbutilrenderutilStorePath}/lib" | ||||||
|  |     XORG_XCB_UTIL_KEYSYMS_PATH="${xorgxcbutilkeysymsStorePath}/lib" | ||||||
|  |     XORG_XCB_UTIL_ERRORS_PATH="${xorgxcbutilerrorsStorePath}/lib" | ||||||
| 
 | 
 | ||||||
|     export RPDFUZZ_PATH="${rapidfuzzStorePath}/lib/python3.12/site-packages" |     LD_LIBRARY_PATH="$LD_LIBRARY_PATH:$QTBASE_PATH" | ||||||
|     export QDRKSTYLE_PATH="${qdarkstyleStorePath}/lib/python3.12/site-packages" |     LD_LIBRARY_PATH="$LD_LIBRARY_PATH:$QT_PLUGIN_PATH" | ||||||
|     export QTPY_PATH="${qtpyStorePath}/lib/python3.12/site-packages" |     LD_LIBRARY_PATH="$LD_LIBRARY_PATH:$QT_QPA_PLATFORM_PLUGIN_PATH" | ||||||
|     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=\"" |     LD_LIBRARY_PATH="$LD_LIBRARY_PATH:$LIB_GCC_PATH" | ||||||
|  |     LD_LIBRARY_PATH="$LD_LIBRARY_PATH:$DBUS_PATH" | ||||||
|  |     LD_LIBRARY_PATH="$LD_LIBRARY_PATH:$GLIB_PATH" | ||||||
|  |     LD_LIBRARY_PATH="$LD_LIBRARY_PATH:$ZSTD_PATH" | ||||||
|  |     LD_LIBRARY_PATH="$LD_LIBRARY_PATH:$LIBGL_PATH" | ||||||
|  |     LD_LIBRARY_PATH="$LD_LIBRARY_PATH:$FONTCONFIG_PATH" | ||||||
|  |     LD_LIBRARY_PATH="$LD_LIBRARY_PATH:$FREETYPE_PATH" | ||||||
|  |     LD_LIBRARY_PATH="$LD_LIBRARY_PATH:$LIB_XKB_COMMON_PATH" | ||||||
| 
 | 
 | ||||||
|     PATCH="$PATCH\$RPDFUZZ_PATH" |     LD_LIBRARY_PATH="$LD_LIBRARY_PATH:$XCB_UTIL_CURSOR_PATH" | ||||||
|     PATCH="$PATCH:\$QDRKSTYLE_PATH" |     LD_LIBRARY_PATH="$LD_LIBRARY_PATH:$XORG_LIB_X11_PATH" | ||||||
|     PATCH="$PATCH:\$QTPY_PATH" |     LD_LIBRARY_PATH="$LD_LIBRARY_PATH:$XORG_LIB_XCB_PATH" | ||||||
|     PATCH="$PATCH:\$PYQT6_PATH" |     LD_LIBRARY_PATH="$LD_LIBRARY_PATH:$XORG_XCB_UTIL_IMAGE_PATH" | ||||||
|     PATCH="$PATCH:\$PYQT6_SIP_PATH" |     LD_LIBRARY_PATH="$LD_LIBRARY_PATH:$XORG_XCB_UTIL_WM_PATH" | ||||||
|  |     LD_LIBRARY_PATH="$LD_LIBRARY_PATH:$XORG_XCB_UTIL_RENDER_UTIL_PATH" | ||||||
|  |     LD_LIBRARY_PATH="$LD_LIBRARY_PATH:$XORG_XCB_UTIL_KEYSYMS_PATH" | ||||||
|  |     LD_LIBRARY_PATH="$LD_LIBRARY_PATH:$XORG_XCB_UTIL_ERRORS_PATH" | ||||||
| 
 | 
 | ||||||
|     PATCH="$PATCH\"" |     export LD_LIBRARY_PATH | ||||||
| 
 | 
 | ||||||
|     if grep -q "$PATCH" "$ACTIVATE_SCRIPT_PATH"; then |     RPDFUZZ_PATH="${rapidfuzzStorePath}/lib/python3.12/site-packages" | ||||||
|         echo "venv is already patched." |     QDRKSTYLE_PATH="${qdarkstyleStorePath}/lib/python3.12/site-packages" | ||||||
|     else |     QTPY_PATH="${qtpyStorePath}/lib/python3.12/site-packages" | ||||||
|         echo "patching $ACTIVATE_SCRIPT_PATH to use pyqt6 from nixos..." |     PYQT6_PATH="${pyqt6StorePath}/lib/python3.12/site-packages" | ||||||
|         sed -i "\$i$PATCH" $ACTIVATE_SCRIPT_PATH |     PYQT6_SIP_PATH="${pyqt6SipStorePath}/lib/python3.12/site-packages" | ||||||
|     fi | 
 | ||||||
|  |     PATCH="$PATCH:$RPDFUZZ_PATH" | ||||||
|  |     PATCH="$PATCH:$QDRKSTYLE_PATH" | ||||||
|  |     PATCH="$PATCH:$QTPY_PATH" | ||||||
|  |     PATCH="$PATCH:$PYQT6_PATH" | ||||||
|  |     PATCH="$PATCH:$PYQT6_SIP_PATH" | ||||||
|  | 
 | ||||||
|  |     export PATCH | ||||||
|  | 
 | ||||||
|  |     # Install deps | ||||||
|  |     uv lock | ||||||
| 
 | 
 | ||||||
|     poetry shell |  | ||||||
|   ''; |   ''; | ||||||
| } | } | ||||||
|  |  | ||||||
|  | @ -0,0 +1,125 @@ | ||||||
|  | #!/usr/bin/env python | ||||||
|  | from decimal import ( | ||||||
|  |     Decimal, | ||||||
|  | ) | ||||||
|  | import trio | ||||||
|  | import tractor | ||||||
|  | from datetime import datetime | ||||||
|  | from pprint import pformat | ||||||
|  | from piker.brokers.deribit.api import ( | ||||||
|  |     get_client, | ||||||
|  |     maybe_open_oi_feed, | ||||||
|  | ) | ||||||
|  | 
 | ||||||
|  | def check_if_complete( | ||||||
|  |         oi: dict[str, dict[str, Decimal | None]] | ||||||
|  |     ) -> bool: | ||||||
|  |     return all( | ||||||
|  |         oi[strike]['C'] is not None  | ||||||
|  |         and | ||||||
|  |         oi[strike]['P'] is not None for strike in oi | ||||||
|  |     ) | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | async def max_pain_daemon( | ||||||
|  | ) -> None: | ||||||
|  |     expiry_date: str = input('Please enter a valid expiration date (7feb25): ').upper() | ||||||
|  |     print('Starting little daemon...') | ||||||
|  |     instruments: list[Symbol] = [] | ||||||
|  |     oi_by_strikes: dict[str, dict[str, Decimal]] | ||||||
|  | 
 | ||||||
|  |     def update_oi_by_strikes(msg: tuple): | ||||||
|  |         nonlocal oi_by_strikes | ||||||
|  |         if 'oi' == msg[0]: | ||||||
|  |             strike_price = msg[1]['strike_price'] | ||||||
|  |             option_type = msg[1]['option_type'] | ||||||
|  |             open_interest = msg[1]['open_interest'] | ||||||
|  |             oi_by_strikes.setdefault( | ||||||
|  |                 strike_price, {} | ||||||
|  |             ).update( | ||||||
|  |                 {option_type: open_interest} | ||||||
|  |             ) | ||||||
|  | 
 | ||||||
|  |     def get_max_pain( | ||||||
|  |         oi_by_strikes: dict[str, dict[str, Decimal]] | ||||||
|  |     ) -> dict[str, str | Decimal]: | ||||||
|  |         ''' | ||||||
|  |         This method requires only the strike_prices and oi for call | ||||||
|  |         and puts, the closes list are the same as the strike_prices | ||||||
|  |         the idea is to sum all the calls and puts cash for each strike | ||||||
|  |         and the ITM strikes from that strike, the lowest value is what we  | ||||||
|  |         are looking for the intrinsic value. | ||||||
|  | 
 | ||||||
|  |         ''' | ||||||
|  | 
 | ||||||
|  |         nonlocal timestamp | ||||||
|  |         # We meed to find the lowest value, so we start at  | ||||||
|  |         # infinity to ensure that, and the max_pain must be  | ||||||
|  |         # an amount greater than zero. | ||||||
|  |         total_intrinsic_value: Decimal = Decimal('Infinity') | ||||||
|  |         max_pain: Decimal = Decimal(0) | ||||||
|  |         call_cash: Decimal = Decimal(0) | ||||||
|  |         put_cash: Decimal = Decimal(0) | ||||||
|  |         intrinsic_values: dict[str, dict[str, Decimal]] = {} | ||||||
|  |         closes: list = sorted(Decimal(close) for close in oi_by_strikes) | ||||||
|  | 
 | ||||||
|  |         for strike, oi in oi_by_strikes.items(): | ||||||
|  |             s = Decimal(strike) | ||||||
|  |             call_cash = sum(max(0, (s - c) * oi_by_strikes[str(c)]['C']) for c in closes) | ||||||
|  |             put_cash = sum(max(0, (c - s) * oi_by_strikes[str(c)]['P']) for c in closes) | ||||||
|  | 
 | ||||||
|  |             intrinsic_values[strike] = { | ||||||
|  |                 'C': call_cash, | ||||||
|  |                 'P': put_cash, | ||||||
|  |                 'total': call_cash + put_cash, | ||||||
|  |             } | ||||||
|  | 
 | ||||||
|  |             if intrinsic_values[strike]['total'] < total_intrinsic_value: | ||||||
|  |                 total_intrinsic_value = intrinsic_values[strike]['total'] | ||||||
|  |                 max_pain = s | ||||||
|  | 
 | ||||||
|  |         return { | ||||||
|  |             'timestamp': timestamp, | ||||||
|  |             'expiry_date': expiry_date, | ||||||
|  |             'total_intrinsic_value': total_intrinsic_value, | ||||||
|  |             'max_pain': max_pain, | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |     async with get_client( | ||||||
|  |     ) as client: | ||||||
|  |         instruments = await client.get_instruments( | ||||||
|  |             expiry_date=expiry_date, | ||||||
|  |         ) | ||||||
|  |         oi_by_strikes = client.get_strikes_dict(instruments) | ||||||
|  | 
 | ||||||
|  |     async with maybe_open_oi_feed( | ||||||
|  |         instruments, | ||||||
|  |     ) as oi_feed: | ||||||
|  |         async for msg in oi_feed: | ||||||
|  | 
 | ||||||
|  |             update_oi_by_strikes(msg) | ||||||
|  |             if check_if_complete(oi_by_strikes): | ||||||
|  |                 if 'oi' == msg[0]: | ||||||
|  |                     timestamp = msg[1]['timestamp'] | ||||||
|  |                     max_pain = get_max_pain(oi_by_strikes) | ||||||
|  |                     print('-----------------------------------------------') | ||||||
|  |                     print(f'timestamp:             {datetime.fromtimestamp(max_pain['timestamp'])}') | ||||||
|  |                     print(f'expiry_date:           {max_pain['expiry_date']}') | ||||||
|  |                     print(f'max_pain:              {max_pain['max_pain']}') | ||||||
|  |                     print(f'total intrinsic value: {max_pain['total_intrinsic_value']}') | ||||||
|  |                     print('-----------------------------------------------') | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | async def main(): | ||||||
|  | 
 | ||||||
|  |     async with tractor.open_nursery() as n: | ||||||
|  |          | ||||||
|  |         p: tractor.Portal = await n.start_actor( | ||||||
|  |             'max_pain_daemon', | ||||||
|  |             enable_modules=[__name__], | ||||||
|  |             infect_asyncio=True, | ||||||
|  |         ) | ||||||
|  |         await p.run(max_pain_daemon) | ||||||
|  | 
 | ||||||
|  | if __name__ == '__main__': | ||||||
|  |     trio.run(main) | ||||||
|  | @ -52,12 +52,14 @@ from cryptofeed import FeedHandler | ||||||
| from cryptofeed.defines import ( | from cryptofeed.defines import ( | ||||||
|     DERIBIT, |     DERIBIT, | ||||||
|     L1_BOOK, TRADES, |     L1_BOOK, TRADES, | ||||||
|     OPTION, CALL, PUT |     OPTION, CALL, PUT, | ||||||
|  |     OPEN_INTEREST, | ||||||
| ) | ) | ||||||
| from cryptofeed.symbols import Symbol | from cryptofeed.symbols import Symbol | ||||||
| from cryptofeed.types import ( | from cryptofeed.types import ( | ||||||
|     L1Book, |     L1Book, | ||||||
|     Trade, |     Trade, | ||||||
|  |     OpenInterest, | ||||||
| ) | ) | ||||||
| from piker.brokers import SymbolNotFound | from piker.brokers import SymbolNotFound | ||||||
| from .venues import ( | from .venues import ( | ||||||
|  | @ -110,6 +112,10 @@ def deribit_timestamp(when: datetime) -> int: | ||||||
|     ) |     ) | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
|  | def get_timestamp_int(expiry_date: str) -> int: | ||||||
|  |     return int(time.mktime(time.strptime(expiry_date, '%d%b%y'))) | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
| def str_to_cb_sym(name: str) -> Symbol: | def str_to_cb_sym(name: str) -> Symbol: | ||||||
|     base, strike_price, expiry_date, option_type = name.split('-') |     base, strike_price, expiry_date, option_type = name.split('-') | ||||||
| 
 | 
 | ||||||
|  | @ -122,8 +128,9 @@ def str_to_cb_sym(name: str) -> Symbol: | ||||||
|     else: |     else: | ||||||
|         raise Exception("Couldn\'t parse option type") |         raise Exception("Couldn\'t parse option type") | ||||||
| 
 | 
 | ||||||
|     new_expiry_date = get_values_from_cb_normalized_date(expiry_date) |     new_expiry_date: int = get_timestamp_int( | ||||||
| 
 |         get_values_from_cb_normalized_date(expiry_date) | ||||||
|  |     ) | ||||||
|     return Symbol( |     return Symbol( | ||||||
|         base=base, |         base=base, | ||||||
|         quote=quote, |         quote=quote, | ||||||
|  | @ -143,11 +150,12 @@ def piker_sym_to_cb_sym(name: str) -> Symbol: | ||||||
|     )= tuple( |     )= tuple( | ||||||
|         name.upper().split('-')) |         name.upper().split('-')) | ||||||
| 
 | 
 | ||||||
|  |     new_expiry_date = get_timestamp_int(expiry_date) | ||||||
|     quote: str = base |     quote: str = base | ||||||
| 
 | 
 | ||||||
|     if option_type == 'P': |     if option_type == 'P' or option_type == 'PUT': | ||||||
|         option_type = PUT  |         option_type = PUT  | ||||||
|     elif option_type == 'C': |     elif option_type == 'C' or option_type == 'CALL': | ||||||
|         option_type = CALL |         option_type = CALL | ||||||
|     else: |     else: | ||||||
|         raise Exception("Couldn\'t parse option type") |         raise Exception("Couldn\'t parse option type") | ||||||
|  | @ -158,7 +166,7 @@ def piker_sym_to_cb_sym(name: str) -> Symbol: | ||||||
|         type=OPTION, |         type=OPTION, | ||||||
|         strike_price=strike_price, |         strike_price=strike_price, | ||||||
|         option_type=option_type, |         option_type=option_type, | ||||||
|         expiry_date=expiry_date |         expiry_date=new_expiry_date | ||||||
|     ) |     ) | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
|  | @ -226,16 +234,18 @@ def get_config() -> dict[str, Any]: | ||||||
|         ) |         ) | ||||||
| 
 | 
 | ||||||
|     conf_option = section.get('option', {}) |     conf_option = section.get('option', {}) | ||||||
|     section.clear # clear the dict to reuse it |     conf_log = conf_option.get('log', {}) | ||||||
|     section['deribit'] = {} |     return { | ||||||
|     section['deribit']['key_id'] = conf_option.get('api_key') |         'deribit': { | ||||||
|     section['deribit']['key_secret'] = conf_option.get('api_secret') |             'key_id': conf_option['key_id'], | ||||||
| 
 |             'key_secret': conf_option['key_secret'], | ||||||
|     section['log'] = {} |         }, | ||||||
|     section['log']['filename'] = 'feedhandler.log' |         'log': { | ||||||
|     section['log']['level'] = 'DEBUG' |             'filename': conf_log['filename'], | ||||||
| 
 |             'level': conf_log['level'], | ||||||
|     return section |             'disabled': conf_log['disabled'], | ||||||
|  |         } | ||||||
|  |     } | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| class Client: | class Client: | ||||||
|  | @ -311,6 +321,20 @@ class Client: | ||||||
| 
 | 
 | ||||||
|         return balances |         return balances | ||||||
| 
 | 
 | ||||||
|  |     async def get_currencies( | ||||||
|  |         self, | ||||||
|  | 
 | ||||||
|  |     ) -> list[dict]: | ||||||
|  |         ''' | ||||||
|  |         Return the set of currencies for deribit. | ||||||
|  |         ''' | ||||||
|  |         assets = {} | ||||||
|  |         resp = await self._json_rpc_auth_wrapper( | ||||||
|  |             'public/get_currencies', | ||||||
|  |             params={} | ||||||
|  |         ) | ||||||
|  |         return resp.result | ||||||
|  | 
 | ||||||
|     async def get_assets( |     async def get_assets( | ||||||
|         self, |         self, | ||||||
|         venue: str | None = None, |         venue: str | None = None, | ||||||
|  | @ -323,11 +347,7 @@ class Client: | ||||||
| 
 | 
 | ||||||
|         ''' |         ''' | ||||||
|         assets = {} |         assets = {} | ||||||
|         resp = await self._json_rpc_auth_wrapper( |         currencies = await self.get_currencies() | ||||||
|             'public/get_currencies', |  | ||||||
|             params={} |  | ||||||
|         ) |  | ||||||
|         currencies: list[dict] = resp.result |  | ||||||
|         for currency in currencies: |         for currency in currencies: | ||||||
|             name: str = currency['currency'] |             name: str = currency['currency'] | ||||||
|             tx_tick: Decimal = digits_to_dec(currency['fee_precision']) |             tx_tick: Decimal = digits_to_dec(currency['fee_precision']) | ||||||
|  | @ -359,6 +379,59 @@ class Client: | ||||||
| 
 | 
 | ||||||
|         return flat |         return flat | ||||||
| 
 | 
 | ||||||
|  |     async def get_instruments( | ||||||
|  |         self, | ||||||
|  |         currency: str = 'btc', | ||||||
|  |         kind: str = 'option', | ||||||
|  |         expired: bool = False, | ||||||
|  |         expiry_date: str = None, | ||||||
|  | 
 | ||||||
|  |     ) -> list[Symbol]: | ||||||
|  |         """ | ||||||
|  |         Get instruments for cryptoFeed.FeedHandler. | ||||||
|  |         """ | ||||||
|  |         params: dict[str, str] = { | ||||||
|  |             'currency': currency.upper(), | ||||||
|  |             'kind': kind, | ||||||
|  |             'expired': expired, | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         r: JSONRPCResult = await self._json_rpc_auth_wrapper( | ||||||
|  |             'public/get_instruments', | ||||||
|  |             params, | ||||||
|  |         ) | ||||||
|  |         resp = r.result  | ||||||
|  |         response_list = [] | ||||||
|  | 
 | ||||||
|  |         for i in range(len(resp)): | ||||||
|  |             element = resp[i] | ||||||
|  |             name = f'{element["instrument_name"].split("-")[1]}' | ||||||
|  |             if not expiry_date or name == expiry_date.upper(): | ||||||
|  |                 response_list.append(piker_sym_to_cb_sym(element['instrument_name'])) | ||||||
|  | 
 | ||||||
|  |         return response_list | ||||||
|  | 
 | ||||||
|  |     def get_strikes_dict( | ||||||
|  |         self, | ||||||
|  |         instruments: list[Symbol], | ||||||
|  | 
 | ||||||
|  |     ) -> dict[str, dict[str, Decimal | None]]: | ||||||
|  |         """ | ||||||
|  |         Get a dict with strike prices as keys. | ||||||
|  |         """ | ||||||
|  |          | ||||||
|  |         response: dict[str, dict[str, Decimal | None]] = {} | ||||||
|  | 
 | ||||||
|  |         for i in range(len(instruments)): | ||||||
|  |             element = instruments[i] | ||||||
|  |             strike = f'{str(element).split('-')[1]}' | ||||||
|  |             response[f'{strike}'] = { | ||||||
|  |                 'C': None, | ||||||
|  |                 'P': None, | ||||||
|  |             } | ||||||
|  | 
 | ||||||
|  |         return response | ||||||
|  | 
 | ||||||
|     async def submit_limit( |     async def submit_limit( | ||||||
|         self, |         self, | ||||||
|         symbol: str, |         symbol: str, | ||||||
|  | @ -738,6 +811,116 @@ async def maybe_open_price_feed( | ||||||
|             yield feed |             yield feed | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
|  | async def aio_open_interest_feed_relay( | ||||||
|  |     fh: FeedHandler, | ||||||
|  |     instruments: list[Symbol], | ||||||
|  |     from_trio: asyncio.Queue, | ||||||
|  |     to_trio: trio.abc.SendChannel, | ||||||
|  | ) -> None: | ||||||
|  |     async def _trade( | ||||||
|  |         trade: Trade,  # cryptofeed, NOT ours from `.venues`! | ||||||
|  |         receipt_timestamp: int, | ||||||
|  |     ) -> None: | ||||||
|  |         ''' | ||||||
|  |         Proxy-thru `cryptofeed.FeedHandler` "trades" to `piker`-side. | ||||||
|  | 
 | ||||||
|  |         ''' | ||||||
|  |         to_trio.send_nowait(('trade', trade)) | ||||||
|  | 
 | ||||||
|  | 	# trade and oi are user defined functions that | ||||||
|  | 	# will be called when trade and open interest updates are received | ||||||
|  | 	# data type is not dict, is an object: cryptofeed.types.OpenINterest | ||||||
|  |     async def _oi( | ||||||
|  |         oi: OpenInterest, | ||||||
|  |         receipt_timestamp: int, | ||||||
|  |     ) -> None: | ||||||
|  |         ''' | ||||||
|  |         Proxy-thru `cryptofeed.FeedHandler` "oi" to `piker`-side. | ||||||
|  | 
 | ||||||
|  |         ''' | ||||||
|  |         symbol: Symbol = str_to_cb_sym(oi.symbol) | ||||||
|  |         piker_sym: str = cb_sym_to_deribit_inst(symbol) | ||||||
|  |         ( | ||||||
|  |             base, | ||||||
|  |             expiry_date, | ||||||
|  |             strike_price, | ||||||
|  |             option_type | ||||||
|  |         ) = tuple( | ||||||
|  |             piker_sym.split('-') | ||||||
|  |         ) | ||||||
|  |         msg = { | ||||||
|  |             'timestamp': oi.timestamp, | ||||||
|  |             'strike_price': strike_price, | ||||||
|  |             'option_type': option_type, | ||||||
|  |             'open_interest': Decimal(oi.open_interest), | ||||||
|  |         } | ||||||
|  |         to_trio.send_nowait(('oi', msg)) | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  |     channels = [TRADES, OPEN_INTEREST] | ||||||
|  |     callbacks={TRADES: _trade, OPEN_INTEREST: _oi} | ||||||
|  | 
 | ||||||
|  |     fh.add_feed( | ||||||
|  |         DERIBIT, | ||||||
|  |         channels=channels, | ||||||
|  |         symbols=instruments, | ||||||
|  |         callbacks=callbacks | ||||||
|  |     ) | ||||||
|  | 
 | ||||||
|  |     if not fh.running: | ||||||
|  |         fh.run( | ||||||
|  |             start_loop=False, | ||||||
|  |             install_signal_handlers=False | ||||||
|  |         ) | ||||||
|  | 
 | ||||||
|  |     # sync with trio | ||||||
|  |     to_trio.send_nowait(None) | ||||||
|  | 
 | ||||||
|  |     # run until cancelled | ||||||
|  |     await asyncio.sleep(float('inf')) | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | @acm | ||||||
|  | async def open_oi_feed( | ||||||
|  |     instruments: list[Symbol],  | ||||||
|  | ) -> to_asyncio.LinkedTaskChannel: | ||||||
|  | 
 | ||||||
|  |     fh: FeedHandler | ||||||
|  |     first: None | ||||||
|  |     chan: to_asyncio.LinkedTaskChannel | ||||||
|  |     async with ( | ||||||
|  |         maybe_open_feed_handler() as fh, | ||||||
|  |         to_asyncio.open_channel_from( | ||||||
|  |             partial( | ||||||
|  |                 aio_open_interest_feed_relay, | ||||||
|  |                 fh, | ||||||
|  |                 instruments, | ||||||
|  |             ) | ||||||
|  |         ) as (first, chan) | ||||||
|  |     ): | ||||||
|  |         yield chan | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | @acm | ||||||
|  | async def maybe_open_oi_feed( | ||||||
|  |     instruments: list[Symbol],  | ||||||
|  | ) -> trio.abc.ReceiveStream: | ||||||
|  | 
 | ||||||
|  |     # TODO: add a predicate to maybe_open_context | ||||||
|  |     feed: to_asyncio.LinkedTaskChannel | ||||||
|  |     async with maybe_open_context( | ||||||
|  |         acm_func=open_oi_feed, | ||||||
|  |         kwargs={ | ||||||
|  |             'instruments': instruments | ||||||
|  |         }, | ||||||
|  |         key=f'{instruments[0].base}', | ||||||
|  | 
 | ||||||
|  |     ) as (cache_hit, feed): | ||||||
|  |         if cache_hit: | ||||||
|  |             yield broadcast_receiver(feed, 10) | ||||||
|  |         else: | ||||||
|  |             yield feed | ||||||
|  | 
 | ||||||
| 
 | 
 | ||||||
| # TODO, move all to `.broker` submod! | # TODO, move all to `.broker` submod! | ||||||
| # async def aio_order_feed_relay( | # async def aio_order_feed_relay( | ||||||
|  |  | ||||||
							
								
								
									
										201
									
								
								pyproject.toml
								
								
								
								
							
							
						
						
									
										201
									
								
								pyproject.toml
								
								
								
								
							|  | @ -15,8 +15,8 @@ | ||||||
| # You should have received a copy of the GNU Affero General Public License | # You should have received a copy of the GNU Affero General Public License | ||||||
| # along with this program.  If not, see <https://www.gnu.org/licenses/>. | # along with this program.  If not, see <https://www.gnu.org/licenses/>. | ||||||
| [build-system] | [build-system] | ||||||
| requires = ["poetry-core"] | requires = ["hatchling"] | ||||||
| build-backend = "poetry.core.masonry.api" | build-backend = "hatchling.build" | ||||||
| 
 | 
 | ||||||
| # ------ - ------ | # ------ - ------ | ||||||
| 
 | 
 | ||||||
|  | @ -34,102 +34,14 @@ ignore = [] | ||||||
| 
 | 
 | ||||||
| # ------ - ------ | # ------ - ------ | ||||||
| 
 | 
 | ||||||
| [tool.poetry] |  | ||||||
| name = "piker" |  | ||||||
| version = "0.1.0.alpha0.dev0" |  | ||||||
| description = "trading gear for hackers" |  | ||||||
| authors = ["Tyler Goodlet <goodboy_foss@protonmail.com>"] |  | ||||||
| license = "AGPLv3" |  | ||||||
| readme = "README.rst" |  | ||||||
| 
 |  | ||||||
| # ------ - ------ |  | ||||||
| 
 |  | ||||||
| [tool.poetry.dependencies] |  | ||||||
| async-generator = "^1.10" |  | ||||||
| attrs = "^23.1.0" |  | ||||||
| bidict = "^0.22.1" |  | ||||||
| colorama = "^0.4.6" |  | ||||||
| colorlog = "^6.7.0" |  | ||||||
| ib-insync = "^0.9.86" |  | ||||||
| msgspec = "^0.18.6" |  | ||||||
| numba = "^0.59.0" |  | ||||||
| numpy = "^1.25" |  | ||||||
| polars = "^0.18.13" |  | ||||||
| pygments = "^2.16.1" |  | ||||||
| python = ">=3.11, <3.13" |  | ||||||
| rich = "^13.5.2" |  | ||||||
| # setuptools = "^68.0.0" |  | ||||||
| tomli = "^2.0.1" |  | ||||||
| tomli-w = "^1.0.0" |  | ||||||
| trio-util = "^0.7.0" |  | ||||||
| trio-websocket = "^0.10.3" |  | ||||||
| typer = "^0.9.0" |  | ||||||
| rapidfuzz = "^3.5.2" |  | ||||||
| pdbp = "^1.5.0" |  | ||||||
| trio = "^0.24" |  | ||||||
| pendulum = "^3.0.0" |  | ||||||
| httpx = "^0.27.0" |  | ||||||
| cryptofeed = "^2.4.0" |  | ||||||
| pyarrow = "^17.0.0" |  | ||||||
| 
 |  | ||||||
| tractor = {path = "../tractor", develop = true} |  | ||||||
| websockets = "12.0" |  | ||||||
| [tool.poetry.dependencies.asyncvnc] |  | ||||||
| git = 'https://github.com/pikers/asyncvnc.git' |  | ||||||
| branch = 'main' |  | ||||||
| 
 |  | ||||||
| [tool.poetry.dependencies.tomlkit] |  | ||||||
| develop = true |  | ||||||
| git = 'https://github.com/pikers/tomlkit.git' |  | ||||||
| branch = 'piker_pin' |  | ||||||
| # path = "../tomlkit/" |  | ||||||
| 
 |  | ||||||
| [tool.poetry.group.uis] |  | ||||||
| optional = true |  | ||||||
| [tool.poetry.group.uis.dependencies] |  | ||||||
| # https://python-poetry.org/docs/managing-dependencies/#dependency-groups |  | ||||||
| # TODO: make sure the levenshtein shit compiles on nix.. |  | ||||||
| # rapidfuzz = {extras = ["speedup"], version = "^0.18.0"} |  | ||||||
| rapidfuzz = "^3.2.0" |  | ||||||
| qdarkstyle = ">=3.0.2" |  | ||||||
| pyqtgraph = { git = 'https://github.com/pikers/pyqtgraph.git' } |  | ||||||
| 
 |  | ||||||
| # ------ - ------ |  | ||||||
| pyqt6 = "^6.7.0" |  | ||||||
| 
 |  | ||||||
| [tool.poetry.group.dev] |  | ||||||
| optional = true |  | ||||||
| [tool.poetry.group.dev.dependencies] |  | ||||||
| # testing / CI |  | ||||||
| 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. |  | ||||||
| # TODO: add a toolset that makes debugging a `pikerd` service |  | ||||||
| # (tree) easy to hack on directly using more or less the local env: |  | ||||||
| # - xonsh + xxh |  | ||||||
| # - rsyscall + pdbp |  | ||||||
| # - actor runtime control console like BEAM/OTP |  | ||||||
| 
 |  | ||||||
| # ------ - ------ |  | ||||||
| 
 |  | ||||||
| # TODO: add an `--only daemon` group for running non-ui / pikerd |  | ||||||
| # service tree in distributed mode B) |  | ||||||
| # https://python-poetry.org/docs/managing-dependencies/#installing-group-dependencies |  | ||||||
| # [tool.poetry.group.daemon.dependencies] |  | ||||||
| 
 |  | ||||||
| [tool.poetry.scripts] |  | ||||||
| piker = 'piker.cli:cli' |  | ||||||
| pikerd = 'piker.cli:pikerd' |  | ||||||
| ledger = 'piker.accounting.cli:ledger' |  | ||||||
| 
 |  | ||||||
| 
 |  | ||||||
| [project] | [project] | ||||||
|  | name = "piker" | ||||||
|  | version = "0.1.0a0dev0" | ||||||
|  | description = "trading gear for hackers" | ||||||
|  | authors = [{ name = "Tyler Goodlet", email = "goodboy_foss@protonmail.com" }] | ||||||
|  | requires-python = ">=3.12, <3.13" | ||||||
|  | license = "AGPL-3.0-or-later" | ||||||
|  | readme = "README.rst" | ||||||
| keywords = [ | keywords = [ | ||||||
|     "async", |     "async", | ||||||
|     "trading", |     "trading", | ||||||
|  | @ -138,15 +50,98 @@ keywords=[ | ||||||
|     "charting", |     "charting", | ||||||
| ] | ] | ||||||
| classifiers = [ | classifiers = [ | ||||||
|   'Development Status :: 3 - Alpha', |     "Development Status :: 3 - Alpha", | ||||||
|     "License :: OSI Approved :: GNU Affero General Public License v3 or later (AGPLv3+)", |     "License :: OSI Approved :: GNU Affero General Public License v3 or later (AGPLv3+)", | ||||||
|   'Operating System :: POSIX :: Linux', |     "Operating System :: POSIX :: Linux", | ||||||
|     "Programming Language :: Python :: Implementation :: CPython", |     "Programming Language :: Python :: Implementation :: CPython", | ||||||
|     "Programming Language :: Python :: 3 :: Only", |     "Programming Language :: Python :: 3 :: Only", | ||||||
|     "Programming Language :: Python :: 3.11", |     "Programming Language :: Python :: 3.11", | ||||||
|     "Programming Language :: Python :: 3.12", |     "Programming Language :: Python :: 3.12", | ||||||
|   'Intended Audience :: Financial and Insurance Industry', |     "Intended Audience :: Financial and Insurance Industry", | ||||||
|   'Intended Audience :: Science/Research', |     "Intended Audience :: Science/Research", | ||||||
|   'Intended Audience :: Developers', |     "Intended Audience :: Developers", | ||||||
|   'Intended Audience :: Education', |     "Intended Audience :: Education", | ||||||
| ] | ] | ||||||
|  | dependencies = [ | ||||||
|  |     "async-generator >=1.10, <2.0.0", | ||||||
|  |     "attrs >=23.1.0, <24.0.0", | ||||||
|  |     "bidict >=0.22.1, <0.23.0", | ||||||
|  |     "colorama >=0.4.6, <0.5.0", | ||||||
|  |     "colorlog >=6.7.0, <7.0.0", | ||||||
|  |     "ib-insync >=0.9.86, <0.10.0", | ||||||
|  |     "numba >=0.59.0, <0.60.0", | ||||||
|  |     "numpy >=1.25, <2.0", | ||||||
|  |     "polars >=0.18.13, <0.19.0", | ||||||
|  |     "pygments >=2.16.1, <3.0.0", | ||||||
|  |     "rich >=13.5.2, <14.0.0", | ||||||
|  |     "tomli >=2.0.1, <3.0.0", | ||||||
|  |     "tomli-w >=1.0.0, <2.0.0", | ||||||
|  |     "trio-util >=0.7.0, <0.8.0", | ||||||
|  |     "trio-websocket >=0.10.3, <0.11.0", | ||||||
|  |     "typer >=0.9.0, <1.0.0", | ||||||
|  |     "rapidfuzz >=3.5.2, <4.0.0", | ||||||
|  |     "pdbp >=1.5.0, <2.0.0", | ||||||
|  |     "trio >=0.24, <0.25", | ||||||
|  |     "pendulum >=3.0.0, <4.0.0", | ||||||
|  |     "httpx >=0.27.0, <0.28.0", | ||||||
|  |     "cryptofeed >=2.4.0, <3.0.0", | ||||||
|  |     "pyarrow >=17.0.0, <18.0.0", | ||||||
|  |     "websockets ==12.0", | ||||||
|  |     "msgspec", | ||||||
|  |     "tractor", | ||||||
|  |     "asyncvnc", | ||||||
|  |     "tomlkit", | ||||||
|  | ] | ||||||
|  | 
 | ||||||
|  | [project.optional-dependencies] | ||||||
|  | uis = [ | ||||||
|  |     # https://docs.astral.sh/uv/concepts/projects/dependencies/#optional-dependencies | ||||||
|  |     # TODO: make sure the levenshtein shit compiles on nix.. | ||||||
|  |     # rapidfuzz = {extras = ["speedup"], version = "^0.18.0"} | ||||||
|  |     "rapidfuzz >=3.2.0, <4.0.0", | ||||||
|  |     "qdarkstyle >=3.0.2, <4.0.0", | ||||||
|  |     "pyqt6 >=6.7.0, <7.0.0", | ||||||
|  |     "pyqtgraph", | ||||||
|  |      | ||||||
|  |     # ------ - ------ | ||||||
|  |      | ||||||
|  |     # TODO: add an `--only daemon` group for running non-ui / pikerd | ||||||
|  |     # service tree in distributed mode B) | ||||||
|  |     # https://docs.astral.sh/uv/concepts/projects/dependencies/#optional-dependencies | ||||||
|  |     # [project.optional-dependencies] | ||||||
|  | ] | ||||||
|  | 
 | ||||||
|  | [dependency-groups] | ||||||
|  | dev = [ | ||||||
|  |     "pytest >=6.0.0, <7.0.0", | ||||||
|  |     "elasticsearch >=8.9.0, <9.0.0", | ||||||
|  |     "xonsh >=0.14.2, <0.15.0", | ||||||
|  |     "prompt-toolkit ==3.0.40", | ||||||
|  |     "cython >=3.0.0, <4.0.0", | ||||||
|  |     "greenback >=1.1.1, <2.0.0", | ||||||
|  |     # console ehancements and eventually remote debugging | ||||||
|  |     # extras/helpers. | ||||||
|  |     # TODO: add a toolset that makes debugging a `pikerd` service | ||||||
|  |     # (tree) easy to hack on directly using more or less the local env: | ||||||
|  |     # - xonsh + xxh | ||||||
|  |     # - rsyscall + pdbp | ||||||
|  |     # - actor runtime control console like BEAM/OTP | ||||||
|  | ] | ||||||
|  | 
 | ||||||
|  | [project.scripts] | ||||||
|  | piker = "piker.cli:cli" | ||||||
|  | pikerd = "piker.cli:pikerd" | ||||||
|  | ledger = "piker.accounting.cli:ledger" | ||||||
|  | 
 | ||||||
|  | [tool.hatch.build.targets.sdist] | ||||||
|  | include = ["piker"] | ||||||
|  | 
 | ||||||
|  | [tool.hatch.build.targets.wheel] | ||||||
|  | include = ["piker"] | ||||||
|  | 
 | ||||||
|  | [tool.uv.sources] | ||||||
|  | pyqtgraph = { git = "https://github.com/pikers/pyqtgraph.git" } | ||||||
|  | asyncvnc = { git = "https://github.com/pikers/asyncvnc.git", branch = "main" } | ||||||
|  | tomlkit = { git = "https://github.com/pikers/tomlkit.git", branch ="piker_pin" } | ||||||
|  | msgspec = { git = "https://github.com/jcrist/msgspec.git" } | ||||||
|  | tractor = { path = "../tractor" } | ||||||
|  |  | ||||||
		Loading…
	
		Reference in New Issue