Compare commits

..

4 Commits

Author SHA1 Message Date
Tyler Goodlet d767be9bd9 `.tsp._history`: drop `feed_is_live` syncing, another seg flag
The `await feed_is_live.wait()` is more or less pointless and would only
cause slower startup afaig (as-far-as-i-grok) so i'm masking it here.
This also removes the final `strict_exception_groups=False` use from the
non-tests code base, flipping to the `tractor.trionics` collapser once
and for all!
2025-10-02 19:53:08 -04:00
Tyler Goodlet 4e8717262c Woops, keep `np2pl` exposed from `.tsp` 2025-10-02 14:14:28 -04:00
Tyler Goodlet e3d87046f9 accounting.calc: `.error()` on bad txn-time fields..
Since i'm seeing IB records with a `None` value and i don't want to be
debugging every time order-mode boots up..

Also use `pdb=debug_mode` in `.open_ledger_dfs()`
2025-10-02 12:17:57 -04:00
Tyler Goodlet 956b974d6e Factor to a new `.tsp._history` sub-mod
Cleaning out the `piker.tsp` pkg-mod to be only the (re)exports needed
for `._anal`/`._history` refs-use elsewhere!
2025-09-29 16:41:08 -04:00
8 changed files with 105 additions and 291 deletions

View File

@ -1,5 +1,6 @@
################
# ---- CEXY ---- # ---- CEXY ----
################
[binance] [binance]
accounts.paper = 'paper' accounts.paper = 'paper'
@ -12,41 +13,28 @@ accounts.spot = 'spot'
spot.use_testnet = false spot.use_testnet = false
spot.api_key = '' spot.api_key = ''
spot.api_secret = '' spot.api_secret = ''
# ------ binance ------
[deribit] [deribit]
# std assets
key_id = '' key_id = ''
key_secret = '' key_secret = ''
# options
accounts.option = 'option'
option.use_testnet = false
option.key_id = ''
option.key_secret = ''
# aux logging from `cryptofeed`
option.log.filename = 'cryptofeed.log'
option.log.level = 'DEBUG'
option.log.disabled = true
# ------ deribit ------
[kraken] [kraken]
key_descr = '' key_descr = ''
api_key = '' api_key = ''
secret = '' secret = ''
# ------ kraken ------
[kucoin] [kucoin]
key_id = '' key_id = ''
key_secret = '' key_secret = ''
key_passphrase = '' key_passphrase = ''
# ------ kucoin ------
################
# -- BROKERZ --- # -- BROKERZ ---
################
[questrade] [questrade]
refresh_token = '' refresh_token = ''
access_token = '' access_token = ''
@ -54,55 +42,44 @@ api_server = 'https://api06.iq.questrade.com/'
expires_in = 1800 expires_in = 1800
token_type = 'Bearer' token_type = 'Bearer'
expires_at = 1616095326.355846 expires_at = 1616095326.355846
# ------ questrade ------
[ib] [ib]
# define the (set of) host-port socketaddrs that
# brokerd.ib will scan to connect to an API endpoint
# (ib-gw or ib-tws listening instances)
hosts = [ hosts = [
'127.0.0.1', '127.0.0.1',
] ]
# XXX: the order in which ports will be scanned
# (by the `brokerd` daemon-actor)
# is determined # by the line order here.
# TODO: when we eventually spawn gateways in our
# container, we can just dynamically allocate these
# using IBC.
ports = [ ports = [
4002, # gw 4002, # gw
7497, # tws 7497, # tws
] ]
# When API endpoints are being scanned durin startup, the order # XXX: for a paper account the flex web query service
# of user-defined-account "names" (as defined below) here # is not supported so you have to manually download
# determines which py-client connection is given priority to be # and XML report and put it in a location that can be
# used for data-feed-requests by according to whichever client # accessed by the ``brokerd.ib`` backend code for parsing.
# connected to an API endpoing which reported the equivalent flex_token = ''
# account number for that name. flex_trades_query_id = '' # live account
# when clients are being scanned this determines
# which clients are preferred to be used for data
# feeds based on the order of account names, if
# detected as active on an API client.
prefer_data_account = [ prefer_data_account = [
'paper', 'paper',
'margin', 'margin',
'ira', 'ira',
] ]
# For long-term trades txn (transaction) history
# processing (i.e your txn ledger with IB) you can
# (automatically for live accounts) query the FLEX
# report system for past history.
#
# (For paper accounts the web query service
# is not supported so you have to manually download
# an XML report and put it in a location that can be
# accessed by our `brokerd.ib` backend code for parsing).
#
flex_token = ''
flex_trades_query_id = '' # live account
# define "aliases" (names) for each account number
# such that the names can be reffed and logged throughout
# `piker.accounting` subsys and more easily
# referred to by the user.
#
# These keys will be the set exposed through the order-mode
# account-selection UI so that numbers are never shown.
[ib.accounts] [ib.accounts]
paper = 'DU0000000' # <- literal account # # the order in which accounts will be selectable
margin = 'U0000000' # in the order mode UI (if found via clients during
ira = 'U0000000' # API-app scanning)when a new symbol is loaded.
# ------ ib ------ paper = 'XX0000000'
margin = 'X0000000'
ira = 'X0000000'

View File

@ -1,9 +1,7 @@
[network] [network]
pikerd = [ tsdb.backend = 'marketstore'
'/ipv4/127.0.0.1/tcp/6116', # std localhost daemon-actor tree tsdb.host = 'localhost'
# '/uds/6116', # TODO std uds socket file tsdb.grpc_port = 5995
]
[ui] [ui]
# set custom font + size which will scale entire UI # set custom font + size which will scale entire UI

View File

@ -1,138 +1,30 @@
running ``ib`` gateway in ``docker`` running ``ib`` gateway in ``docker``
------------------------------------ ------------------------------------
We have a config based on a well maintained community We have a config based on the (now defunct)
image from `@gnzsnz`: image from "waytrade":
https://github.com/gnzsnz/ib-gateway-docker https://github.com/waytrade/ib-gateway-docker
To startup this image with our custom settings
To startup this image simply run the command:: simply run the command::
docker compose up docker compose up
(For further usage^ see the official `docker-compose`_ docs) And you should have the following socket-available services:
- ``x11vnc1@127.0.0.1:3003``
And you should have the following socket-available services by
default:
- ``x11vnc1 @ 127.0.0.1:5900``
- ``ib-gw@127.0.0.1:4002`` - ``ib-gw@127.0.0.1:4002``
You can now attach to the container via a VNC client with password-auth; You can attach to the container via a VNC client
here is an example using ``vncclient`` on ``linux``:: without password auth.
vncviewer localhost:5900 SECURITY STUFF!?!?!
-------------------
now enter the pw (password) you set via an (see second code blob) Though "``ib``" claims they host filter connections outside
`.env file`_ or pw-file according to the `credentials section`_. localhost (aka ``127.0.0.1``) it's probably better if you filter
the socket at the OS level using a stateless firewall rule::
If you want to change away from their default config see the example
`docker-compose.yml`-config issue and config-section of the readme,
- https://github.com/gnzsnz/ib-gateway-docker?tab=readme-ov-file#configuration
- https://github.com/gnzsnz/ib-gateway-docker/discussions/103
.. _.env file: https://github.com/gnzsnz/ib-gateway-docker?tab=readme-ov-file#how-to-use-it
.. _docker-compose: https://docs.docker.com/compose/
.. _credentials section: https://github.com/gnzsnz/ib-gateway-docker?tab=readme-ov-file#credentials
Connecting to the API from `piker`
---------------------------------
In order to expose the container's API endpoint to the
`brokerd/datad/ib` actor, we need to add a section to the user's
`brokers.toml` config (note the below is similar to the repo-shipped
template file),
.. code:: toml
[ib]
# define the (set of) host-port socketaddrs that
# brokerd.ib will scan to connect to an API endpoint
# (ib-gw or ib-tws listening instances)
hosts = [
'127.0.0.1',
]
ports = [
4002, # gw
7497, # tws
]
# When API endpoints are being scanned durin startup, the order
# of user-defined-account "names" (as defined below) here
# determines which py-client connection is given priority to be
# used for data-feed-requests by according to whichever client
# connected to an API endpoing which reported the equivalent
# account number for that name.
prefer_data_account = [
'paper',
'margin',
'ira',
]
# define "aliases" (names) for each account number
# such that the names can be reffed and logged throughout
# `piker.accounting` subsys and more easily
# referred to by the user.
#
# These keys will be the set exposed through the order-mode
# account-selection UI so that numbers are never shown.
[ib.accounts]
paper = 'XX0000000'
margin = 'X0000000'
ira = 'X0000000'
the broker daemon can also connect to the container's VNC server for
added functionalies including,
- viewing the API endpoint program's GUI for manual interventions,
- workarounds for historical data throttling using hotkey hacks,
Add a further section to `brokers.toml` which maps each API-ep's
port to a table of VNC server connection info like,
.. code:: toml
[ib.vnc_addrs]
4002 = {host = 'localhost', port = 5900, pw = 'doggy'}
The `pw = 'doggy'` here ^ should the same value as the particular
container instances `.env` file setting (when it was run),
.. code:: ini
VNC_SERVER_PASSWORD='doggy'
IF you also want to run ``TWS``
-------------------------------
You can also run it containerized,
https://github.com/gnzsnz/ib-gateway-docker?tab=readme-ov-file#using-tws
SECURITY stuff (advanced, only if you're paranoid)
--------------------------------------------------
First and foremost if doing a "distributed" container setup where you
run the ``ib-gw`` docker container and your connecting API client
(likely ``ib_async`` from python) on **different hosts** be sure to
read the `security considerations`_ section!
And for a further (somewhat paranoid) perspective from
a long-time-ago serious devops eng..
Though "``ib``" claims they filter remote host connections outside
``localhost`` (aka ``127.0.0.1`` on ipv4) it's prolly justified if
you'd like to filter the socket at the *OS level* using a stateless
firewall rule::
ip rule add not unicast iif lo to 0.0.0.0/0 dport 4002 ip rule add not unicast iif lo to 0.0.0.0/0 dport 4002
We will soon have this baked into our own custom image but for
We will soon have this either baked into our own custom derivative now you'll have to do it urself dawgy.
image (or patched into the current upstream one after further testin)
but for now you'll have to do it urself, diggity dawg.
.. _security considerations: https://github.com/gnzsnz/ib-gateway-docker?tab=readme-ov-file#security-considerations

View File

@ -1,15 +1,10 @@
# a community maintained IB API container! # rework from the original @
# # https://github.com/waytrade/ib-gateway-docker/blob/master/docker-compose.yml
# https://github.com/gnzsnz/ib-gateway-docker version: "3.5"
#
# For piker we (currently) include some minor deviations
# for some config files in the `volumes` section.
#
# See full configuration settings @
# - https://github.com/gnzsnz/ib-gateway-docker?tab=readme-ov-file#configuration
# - https://github.com/gnzsnz/ib-gateway-docker/discussions/103
services: services:
ib_gw_paper: ib_gw_paper:
# apparently java is a mega cukc: # apparently java is a mega cukc:
@ -55,22 +50,16 @@ services:
target: /root/scripts/run_x11_vnc.sh target: /root/scripts/run_x11_vnc.sh
read_only: true read_only: true
# NOTE: an alt method to fill these out is to # NOTE:to fill these out, define an `.env` file in the same dir as
# define an `.env` file in the same dir as # this compose file which looks something like:
# this compose file. # TWS_USERID='myuser'
# TWS_PASSWORD='guest'
environment: environment:
TWS_USERID: ${TWS_USERID} TWS_USERID: ${TWS_USERID}
# TWS_USERID: 'myuser'
TWS_PASSWORD: ${TWS_PASSWORD} TWS_PASSWORD: ${TWS_PASSWORD}
# TWS_PASSWORD: 'guest' TRADING_MODE: 'paper'
TRADING_MODE: ${TRADING_MODE} VNC_SERVER_PASSWORD: 'doggy'
# TRADING_MODE: 'paper' VNC_SERVER_PORT: '3003'
VNC_SERVER_PASSWORD: ${VNC_SERVER_PASSWORD}
# VNC_SERVER_PASSWORD: 'doggy'
# TODO, see if we can get this supported like it
# was on the old `waytrade` image?
# VNC_SERVER_PORT: '3003'
# ports: # ports:
# - target: 4002 # - target: 4002
@ -87,9 +76,6 @@ services:
# - "127.0.0.1:4002:4002" # - "127.0.0.1:4002:4002"
# - "127.0.0.1:5900:5900" # - "127.0.0.1:5900:5900"
# TODO, a masked but working example of dual paper + live
# ib-gw instances running in a single app run!
#
# ib_gw_live: # ib_gw_live:
# image: waytrade/ib-gateway:1012.2i # image: waytrade/ib-gateway:1012.2i
# restart: no # restart: no

View File

@ -102,7 +102,7 @@ class Pair(Struct, frozen=True, kw_only=True):
# https://developers.binance.com/docs/binance-spot-api-docs#2025-08-26 # https://developers.binance.com/docs/binance-spot-api-docs#2025-08-26
# will become non-optional 2025-08-28? # will become non-optional 2025-08-28?
# https://developers.binance.com/docs/binance-spot-api-docs#future-changes # https://developers.binance.com/docs/binance-spot-api-docs#future-changes
pegInstructionsAllowed: bool = False pegInstructionsAllowed: bool|None = None
filters: dict[ filters: dict[
str, str,

View File

@ -38,6 +38,7 @@ from piker.brokers._util import get_logger
if TYPE_CHECKING: if TYPE_CHECKING:
from .api import Client from .api import Client
from ib_insync import IB
import i3ipc import i3ipc
log = get_logger('piker.brokers.ib') log = get_logger('piker.brokers.ib')
@ -61,7 +62,7 @@ no_setup_msg:str = (
def try_xdo_manual( def try_xdo_manual(
client: Client, vnc_sockaddr: str,
): ):
''' '''
Do the "manual" `xdo`-based screen switch + click Do the "manual" `xdo`-based screen switch + click
@ -78,7 +79,6 @@ def try_xdo_manual(
_reset_tech = 'i3ipc_xdotool' _reset_tech = 'i3ipc_xdotool'
return True return True
except OSError: except OSError:
vnc_sockaddr: str = client.conf.vnc_addrs
log.exception( log.exception(
no_setup_msg.format(vnc_sockaddr=vnc_sockaddr) no_setup_msg.format(vnc_sockaddr=vnc_sockaddr)
) )
@ -86,6 +86,7 @@ def try_xdo_manual(
async def data_reset_hack( async def data_reset_hack(
# vnc_host: str,
client: Client, client: Client,
reset_type: Literal['data', 'connection'], reset_type: Literal['data', 'connection'],
@ -117,24 +118,36 @@ async def data_reset_hack(
that need to be wrangle. that need to be wrangle.
''' '''
ib_client: IB = client.ib
# look up any user defined vnc socket address mapped from # look up any user defined vnc socket address mapped from
# a particular API socket port. # a particular API socket port.
vnc_addrs: tuple[str]|None = client.conf.get('vnc_addrs') api_port: str = str(ib_client.client.port)
if not vnc_addrs: vnc_host: str
vnc_port: int
vnc_sockaddr: tuple[str] | None = client.conf.get('vnc_addrs')
if not vnc_sockaddr:
log.warning( log.warning(
no_setup_msg.format(vnc_sockaddr=client.conf) no_setup_msg.format(vnc_sockaddr=vnc_sockaddr)
+ +
'REQUIRES A `vnc_addrs: array` ENTRY' 'REQUIRES A `vnc_addrs: array` ENTRY'
) )
vnc_host, vnc_port = vnc_sockaddr.get(
api_port,
('localhost', 3003)
)
global _reset_tech global _reset_tech
match _reset_tech: match _reset_tech:
case 'vnc': case 'vnc':
try: try:
await tractor.to_asyncio.run_task( await tractor.to_asyncio.run_task(
partial( partial(
vnc_click_hack, vnc_click_hack,
client=client, host=vnc_host,
port=vnc_port,
) )
) )
except ( except (
@ -145,31 +158,29 @@ async def data_reset_hack(
import i3ipc # noqa (since a deps dynamic check) import i3ipc # noqa (since a deps dynamic check)
except ModuleNotFoundError: except ModuleNotFoundError:
log.warning( log.warning(
no_setup_msg.format(vnc_sockaddr=client.conf) no_setup_msg.format(vnc_sockaddr=vnc_sockaddr)
) )
return False return False
# XXX, Xorg only workaround.. if vnc_host not in {
# TODO? remove now that we have `pyvnc`? 'localhost',
# if vnc_host not in { '127.0.0.1',
# 'localhost', }:
# '127.0.0.1', focussed, matches = i3ipc_fin_wins_titled()
# }: if not matches:
# focussed, matches = i3ipc_fin_wins_titled() log.warning(
# if not matches: no_setup_msg.format(vnc_sockaddr=vnc_sockaddr)
# log.warning( )
# no_setup_msg.format(vnc_sockaddr=vnc_sockaddr) return False
# ) else:
# return False try_xdo_manual(vnc_sockaddr)
# else:
# try_xdo_manual(vnc_sockaddr)
# localhost but no vnc-client or it borked.. # localhost but no vnc-client or it borked..
else: else:
try_xdo_manual(client) try_xdo_manual(vnc_sockaddr)
case 'i3ipc_xdotool': case 'i3ipc_xdotool':
try_xdo_manual(client) try_xdo_manual(vnc_sockaddr)
# i3ipc_xdotool_manual_click_hack() # i3ipc_xdotool_manual_click_hack()
case _ as tech: case _ as tech:
@ -180,55 +191,15 @@ async def data_reset_hack(
async def vnc_click_hack( async def vnc_click_hack(
client: Client, host: str,
reset_type: str = 'data', port: int,
pw: str|None = None, reset_type: str = 'data'
) -> None: ) -> None:
''' '''
Reset the data or network connection for the VNC attached Reset the data or network connection for the VNC attached
ib-gateway using a (magic) keybinding combo. ib-gateway using a (magic) keybinding combo.
A vnc-server password can be set either by an input `pw` param or
set in the client's config with the latter loaded from the user's
`brokers.toml` in a vnc-addrs-port-mapping section,
.. code:: toml
[ib.vnc_addrs]
4002 = {host = 'localhost', port = 5900, pw = 'doggy'}
''' '''
api_port: str = str(client.ib.client.port)
conf: dict = client.conf
vnc_addrs: dict[int, tuple] = conf.get('vnc_addrs')
if not vnc_addrs:
return None
addr_entry: dict|tuple = vnc_addrs.get(
api_port,
('localhost', 5900) # a typical default
)
if pw is None:
match addr_entry:
case (
host,
port,
):
pass
case {
'host': host,
'port': port,
'pw': pw
}:
pass
case _:
raise ValueError(
f'Invalid `ib.vnc_addrs` entry ?\n'
f'{addr_entry!r}\n'
)
try: try:
from pyvnc import ( from pyvnc import (
AsyncVNCClient, AsyncVNCClient,
@ -255,7 +226,7 @@ async def vnc_click_hack(
VNCConfig( VNCConfig(
host=host, host=host,
port=port, port=port,
password=pw, password='doggy',
) )
) )
async with client: async with client:

View File

@ -214,9 +214,7 @@ async def open_history_client(
# could be trying to retreive bars over weekend # could be trying to retreive bars over weekend
if out is None: if out is None:
log.error( log.error(f"Can't grab bars starting at {end_dt}!?!?")
f"No bars starting at {end_dt!r} !?!?"
)
if ( if (
end_dt end_dt
and head_dt and head_dt

View File

@ -43,6 +43,7 @@ from typing import (
import numpy as np import numpy as np
from .. import config from .. import config
from ..service import ( from ..service import (
check_for_service, check_for_service,
@ -161,10 +162,7 @@ class StorageConnectionError(ConnectionError):
''' '''
def get_storagemod( def get_storagemod(name: str) -> ModuleType:
name: str,
) -> ModuleType:
mod: ModuleType = import_module( mod: ModuleType = import_module(
'.' + name, '.' + name,
'piker.storage', 'piker.storage',
@ -179,10 +177,7 @@ def get_storagemod(
async def open_storage_client( async def open_storage_client(
backend: str | None = None, backend: str | None = None,
) -> tuple[ ) -> tuple[ModuleType, StorageClient]:
ModuleType,
StorageClient,
]:
''' '''
Load the ``StorageClient`` for named backend. Load the ``StorageClient`` for named backend.
@ -282,10 +277,7 @@ async def open_tsdb_client(
from ..data.feed import maybe_open_feed from ..data.feed import maybe_open_feed
async with ( async with (
open_storage_client() as ( open_storage_client() as (_, storage),
_,
storage,
),
maybe_open_feed( maybe_open_feed(
[fqme], [fqme],
@ -293,7 +285,7 @@ async def open_tsdb_client(
) as feed, ) as feed,
): ):
profiler(f'opened feed for {fqme!r}') profiler(f'opened feed for {fqme}')
# to_append = feed.hist_shm.array # to_append = feed.hist_shm.array
# to_prepend = None # to_prepend = None