Compare commits
44 Commits
d3ae2b26f6
...
793a454463
| Author | SHA1 | Date |
|---|---|---|
|
|
793a454463 | |
|
|
b8b4f1b80f | |
|
|
1cf041d8e6 | |
|
|
831b6cfb21 | |
|
|
e5f7e8de9d | |
|
|
871bb2620e | |
|
|
de980a69e0 | |
|
|
ab9f01caf2 | |
|
|
d85632ba9b | |
|
|
8294ca6487 | |
|
|
87385a4e2d | |
|
|
b3c5478017 | |
|
|
6c9a78c5a0 | |
|
|
da223f7a55 | |
|
|
49fe0a3398 | |
|
|
29fc3b8a8b | |
|
|
1bfe777637 | |
|
|
c694d915f1 | |
|
|
c120cb51a4 | |
|
|
7c20231f16 | |
|
|
d809c79788 | |
|
|
9f2f8a1664 | |
|
|
9f141635d1 | |
|
|
0604ca7c82 | |
|
|
82c2256271 | |
|
|
a743fa28b5 | |
|
|
07fbe859c3 | |
|
|
db0872e350 | |
|
|
878002aee0 | |
|
|
c9e6510535 | |
|
|
4cae3778c1 | |
|
|
ff49ff0376 | |
|
|
b884febd5f | |
|
|
291508a9b1 | |
|
|
7498c221a8 | |
|
|
64828d2fe1 | |
|
|
1e6fa8675d | |
|
|
51fb871f57 | |
|
|
ffd6438b88 | |
|
|
5449141ec4 | |
|
|
5337f8abee | |
|
|
0329a6d852 | |
|
|
ff045f699f | |
|
|
6d6ca1a908 |
45
README.rst
45
README.rst
|
|
@ -88,22 +88,57 @@ a sane install with `uv`
|
|||
************************
|
||||
bc why install with `python` when you can faster with `rust` ::
|
||||
|
||||
uv lock
|
||||
uv sync
|
||||
|
||||
# ^ astral's docs,
|
||||
# https://docs.astral.sh/uv/concepts/projects/sync/
|
||||
|
||||
include all GUIs (ex. for charting)::
|
||||
|
||||
uv sync --extra uis
|
||||
|
||||
AND with all our hacking tools and WIP integrations::
|
||||
|
||||
uv sync --dev --all-extras
|
||||
|
||||
|
||||
hacky install on nixos
|
||||
**********************
|
||||
Ensure you can run the root-daemon::
|
||||
|
||||
uv run pikerd [-l info --pdb]
|
||||
|
||||
|
||||
install on nix(os)
|
||||
******************
|
||||
``NixOS`` is our core devs' distro of choice for which we offer
|
||||
a stringently defined development shell envoirment that can be loaded with::
|
||||
a stringently defined development shell envoirment that can currently
|
||||
be applied in one of 2 ways::
|
||||
|
||||
# ONLY if running on X11
|
||||
nix-shell default.nix
|
||||
|
||||
Or if you prefer flakes style and a modern DE::
|
||||
|
||||
# ONLY if also running on Wayland
|
||||
nix develop # for default bash
|
||||
nix develop -c uv run xonsh # for @goodboy's preferred sh B)
|
||||
|
||||
|
||||
start a chart
|
||||
*************
|
||||
run a realtime OHLCV chart stand-alone::
|
||||
|
||||
piker -l info chart btcusdt.spot.binance xmrusdt.spot.kraken
|
||||
[uv run] piker -l info chart btcusdt.spot.binance xmrusdt.spot.kraken
|
||||
|
||||
# ^^^ iff you haven't activated the py-env,
|
||||
# - https://docs.astral.sh/uv/concepts/projects/run/
|
||||
#
|
||||
# in order to create an explicit virt-env see,
|
||||
# - https://docs.astral.sh/uv/concepts/projects/layout/#the-project-environment
|
||||
# - https://docs.astral.sh/uv/pip/environments/
|
||||
#
|
||||
# use $UV_PROJECT_ENVIRONMENT to select any non-`.venv/`
|
||||
# as the venv sudir in the repo's root.
|
||||
# - https://docs.astral.sh/uv/reference/environment/#uv_project_environment
|
||||
|
||||
this runs a chart UI (with 1m sampled OHLCV) and shows 2 spot markets from 2 diff cexes
|
||||
overlayed on the same graph. Use of `piker` without first starting
|
||||
|
|
|
|||
37
default.nix
37
default.nix
|
|
@ -11,11 +11,12 @@ let
|
|||
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;
|
||||
pypkgs = python313Packages;
|
||||
qtpyStorePath = lib.getLib pypkgs.qtpy;
|
||||
pyqt6StorePath = lib.getLib pypkgs.pyqt6;
|
||||
pyqt6SipStorePath = lib.getLib pypkgs.pyqt6-sip;
|
||||
rapidfuzzStorePath = lib.getLib pypkgs.rapidfuzz;
|
||||
qdarkstyleStorePath = lib.getLib pypkgs.qdarkstyle;
|
||||
|
||||
xorgLibX11StorePath = lib.getLib xorg.libX11;
|
||||
xorgLibxcbStorePath = lib.getLib xorg.libxcb;
|
||||
|
|
@ -51,12 +52,12 @@ stdenv.mkDerivation {
|
|||
xorg.xcbutilrenderutil
|
||||
|
||||
# Python requirements.
|
||||
python312Full
|
||||
python312Packages.uv
|
||||
python312Packages.qdarkstyle
|
||||
python312Packages.rapidfuzz
|
||||
python312Packages.pyqt6
|
||||
python312Packages.qtpy
|
||||
python313
|
||||
uv
|
||||
pypkgs.qdarkstyle
|
||||
pypkgs.rapidfuzz
|
||||
pypkgs.pyqt6
|
||||
pypkgs.qtpy
|
||||
];
|
||||
src = null;
|
||||
shellHook = ''
|
||||
|
|
@ -113,11 +114,11 @@ stdenv.mkDerivation {
|
|||
|
||||
export LD_LIBRARY_PATH
|
||||
|
||||
RPDFUZZ_PATH="${rapidfuzzStorePath}/lib/python3.12/site-packages"
|
||||
QDRKSTYLE_PATH="${qdarkstyleStorePath}/lib/python3.12/site-packages"
|
||||
QTPY_PATH="${qtpyStorePath}/lib/python3.12/site-packages"
|
||||
PYQT6_PATH="${pyqt6StorePath}/lib/python3.12/site-packages"
|
||||
PYQT6_SIP_PATH="${pyqt6SipStorePath}/lib/python3.12/site-packages"
|
||||
RPDFUZZ_PATH="${rapidfuzzStorePath}/lib/python3.13/site-packages"
|
||||
QDRKSTYLE_PATH="${qdarkstyleStorePath}/lib/python3.13/site-packages"
|
||||
QTPY_PATH="${qtpyStorePath}/lib/python3.13/site-packages"
|
||||
PYQT6_PATH="${pyqt6StorePath}/lib/python3.13/site-packages"
|
||||
PYQT6_SIP_PATH="${pyqt6SipStorePath}/lib/python3.13/site-packages"
|
||||
|
||||
PATCH="$PATCH:$RPDFUZZ_PATH"
|
||||
PATCH="$PATCH:$QDRKSTYLE_PATH"
|
||||
|
|
@ -127,8 +128,8 @@ stdenv.mkDerivation {
|
|||
|
||||
export PATCH
|
||||
|
||||
# Install deps
|
||||
uv lock
|
||||
# install all dev and extras
|
||||
uv sync --dev --all-extras
|
||||
|
||||
'';
|
||||
}
|
||||
|
|
|
|||
123
flake.lock
123
flake.lock
|
|
@ -1,135 +1,24 @@
|
|||
{
|
||||
"nodes": {
|
||||
"flake-utils": {
|
||||
"inputs": {
|
||||
"systems": "systems"
|
||||
},
|
||||
"locked": {
|
||||
"lastModified": 1689068808,
|
||||
"narHash": "sha256-6ixXo3wt24N/melDWjq70UuHQLxGV8jZvooRanIHXw0=",
|
||||
"owner": "numtide",
|
||||
"repo": "flake-utils",
|
||||
"rev": "919d646de7be200f3bf08cb76ae1f09402b6f9b4",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "numtide",
|
||||
"repo": "flake-utils",
|
||||
"type": "github"
|
||||
}
|
||||
},
|
||||
"flake-utils_2": {
|
||||
"inputs": {
|
||||
"systems": "systems_2"
|
||||
},
|
||||
"locked": {
|
||||
"lastModified": 1689068808,
|
||||
"narHash": "sha256-6ixXo3wt24N/melDWjq70UuHQLxGV8jZvooRanIHXw0=",
|
||||
"owner": "numtide",
|
||||
"repo": "flake-utils",
|
||||
"rev": "919d646de7be200f3bf08cb76ae1f09402b6f9b4",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "numtide",
|
||||
"repo": "flake-utils",
|
||||
"type": "github"
|
||||
}
|
||||
},
|
||||
"nix-github-actions": {
|
||||
"inputs": {
|
||||
"nixpkgs": [
|
||||
"poetry2nix",
|
||||
"nixpkgs"
|
||||
]
|
||||
},
|
||||
"locked": {
|
||||
"lastModified": 1688870561,
|
||||
"narHash": "sha256-4UYkifnPEw1nAzqqPOTL2MvWtm3sNGw1UTYTalkTcGY=",
|
||||
"owner": "nix-community",
|
||||
"repo": "nix-github-actions",
|
||||
"rev": "165b1650b753316aa7f1787f3005a8d2da0f5301",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "nix-community",
|
||||
"repo": "nix-github-actions",
|
||||
"type": "github"
|
||||
}
|
||||
},
|
||||
"nixpkgs": {
|
||||
"locked": {
|
||||
"lastModified": 1692174805,
|
||||
"narHash": "sha256-xmNPFDi/AUMIxwgOH/IVom55Dks34u1g7sFKKebxUm0=",
|
||||
"owner": "NixOS",
|
||||
"lastModified": 1765779637,
|
||||
"narHash": "sha256-KJ2wa/BLSrTqDjbfyNx70ov/HdgNBCBBSQP3BIzKnv4=",
|
||||
"owner": "nixos",
|
||||
"repo": "nixpkgs",
|
||||
"rev": "caac0eb6bdcad0b32cb2522e03e4002c8975c62e",
|
||||
"rev": "1306659b587dc277866c7b69eb97e5f07864d8c4",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "NixOS",
|
||||
"owner": "nixos",
|
||||
"ref": "nixos-unstable",
|
||||
"repo": "nixpkgs",
|
||||
"type": "github"
|
||||
}
|
||||
},
|
||||
"poetry2nix": {
|
||||
"inputs": {
|
||||
"flake-utils": "flake-utils_2",
|
||||
"nix-github-actions": "nix-github-actions",
|
||||
"nixpkgs": [
|
||||
"nixpkgs"
|
||||
]
|
||||
},
|
||||
"locked": {
|
||||
"lastModified": 1692048894,
|
||||
"narHash": "sha256-cDw03rso2V4CDc3Mll0cHN+ztzysAvdI8pJ7ybbz714=",
|
||||
"ref": "refs/heads/pyqt6",
|
||||
"rev": "b059ad4c3051f45d6c912e17747aae37a9ec1544",
|
||||
"revCount": 2276,
|
||||
"type": "git",
|
||||
"url": "file:///home/lord_fomo/repos/poetry2nix"
|
||||
},
|
||||
"original": {
|
||||
"type": "git",
|
||||
"url": "file:///home/lord_fomo/repos/poetry2nix"
|
||||
}
|
||||
},
|
||||
"root": {
|
||||
"inputs": {
|
||||
"flake-utils": "flake-utils",
|
||||
"nixpkgs": "nixpkgs",
|
||||
"poetry2nix": "poetry2nix"
|
||||
}
|
||||
},
|
||||
"systems": {
|
||||
"locked": {
|
||||
"lastModified": 1681028828,
|
||||
"narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=",
|
||||
"owner": "nix-systems",
|
||||
"repo": "default",
|
||||
"rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "nix-systems",
|
||||
"repo": "default",
|
||||
"type": "github"
|
||||
}
|
||||
},
|
||||
"systems_2": {
|
||||
"locked": {
|
||||
"lastModified": 1681028828,
|
||||
"narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=",
|
||||
"owner": "nix-systems",
|
||||
"repo": "default",
|
||||
"rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "nix-systems",
|
||||
"repo": "default",
|
||||
"type": "github"
|
||||
"nixpkgs": "nixpkgs"
|
||||
}
|
||||
}
|
||||
},
|
||||
|
|
|
|||
255
flake.nix
255
flake.nix
|
|
@ -1,180 +1,103 @@
|
|||
# NOTE: to convert to a poetry2nix env like this here are the
|
||||
# steps:
|
||||
# - install poetry in your system nix config
|
||||
# - convert the repo to use poetry using `poetry init`:
|
||||
# https://python-poetry.org/docs/basic-usage/#initialising-a-pre-existing-project
|
||||
# - then manually ensuring all deps are converted over:
|
||||
# - add this file to the repo and commit it
|
||||
# -
|
||||
|
||||
# GROKin tips:
|
||||
# - CLI eps are (ostensibly) added via an `entry_points.txt`:
|
||||
# - https://packaging.python.org/en/latest/specifications/entry-points/#file-format
|
||||
# - https://github.com/nix-community/poetry2nix/blob/master/editable.nix#L49
|
||||
# An "impure" template thx to `pyproject.nix`,
|
||||
# https://pyproject-nix.github.io/pyproject.nix/templates.html#impure
|
||||
# https://github.com/pyproject-nix/pyproject.nix/blob/master/templates/impure/flake.nix
|
||||
{
|
||||
description = "piker: trading gear for hackers (pkged with poetry2nix)";
|
||||
description = "An impure `piker` overlay using `uv` with Nix(OS)";
|
||||
|
||||
inputs.flake-utils.url = "github:numtide/flake-utils";
|
||||
inputs.nixpkgs.url = "github:NixOS/nixpkgs/nixos-unstable";
|
||||
|
||||
# see https://github.com/nix-community/poetry2nix/tree/master#api
|
||||
inputs.poetry2nix = {
|
||||
# url = "github:nix-community/poetry2nix";
|
||||
# url = "github:K900/poetry2nix/qt5-explicit-deps";
|
||||
url = "/home/lord_fomo/repos/poetry2nix";
|
||||
|
||||
inputs.nixpkgs.follows = "nixpkgs";
|
||||
inputs = {
|
||||
nixpkgs.url = "github:nixos/nixpkgs/nixos-unstable";
|
||||
};
|
||||
|
||||
outputs = {
|
||||
self,
|
||||
nixpkgs,
|
||||
flake-utils,
|
||||
poetry2nix,
|
||||
}:
|
||||
# TODO: build cross-OS and use the `${system}` var thingy..
|
||||
flake-utils.lib.eachDefaultSystem (system:
|
||||
outputs =
|
||||
{ nixpkgs, ... }:
|
||||
let
|
||||
# use PWD as sources
|
||||
projectDir = ./.;
|
||||
pyproject = ./pyproject.toml;
|
||||
poetrylock = ./poetry.lock;
|
||||
|
||||
# TODO: port to 3.11 and support both versions?
|
||||
python = "python3.10";
|
||||
|
||||
# for more functions and examples.
|
||||
# inherit
|
||||
# (poetry2nix.legacyPackages.${system})
|
||||
# mkPoetryApplication;
|
||||
# pkgs = nixpkgs.legacyPackages.${system};
|
||||
|
||||
pkgs = nixpkgs.legacyPackages.x86_64-linux;
|
||||
lib = pkgs.lib;
|
||||
p2npkgs = poetry2nix.legacyPackages.x86_64-linux;
|
||||
|
||||
# define all pkg overrides per dep, see edgecases.md:
|
||||
# https://github.com/nix-community/poetry2nix/blob/master/docs/edgecases.md
|
||||
# TODO: add these into the json file:
|
||||
# https://github.com/nix-community/poetry2nix/blob/master/overrides/build-systems.json
|
||||
pypkgs-build-requirements = {
|
||||
asyncvnc = [ "setuptools" ];
|
||||
eventkit = [ "setuptools" ];
|
||||
ib-insync = [ "setuptools" "flake8" ];
|
||||
msgspec = [ "setuptools"];
|
||||
pdbp = [ "setuptools" ];
|
||||
pyqt6-sip = [ "setuptools" ];
|
||||
tabcompleter = [ "setuptools" ];
|
||||
tractor = [ "setuptools" ];
|
||||
tricycle = [ "setuptools" ];
|
||||
trio-typing = [ "setuptools" ];
|
||||
trio-util = [ "setuptools" ];
|
||||
xonsh = [ "setuptools" ];
|
||||
};
|
||||
|
||||
# auto-generate override entries
|
||||
p2n-overrides = p2npkgs.defaultPoetryOverrides.extend (self: super:
|
||||
builtins.mapAttrs (package: build-requirements:
|
||||
(builtins.getAttr package super).overridePythonAttrs (old: {
|
||||
buildInputs = (
|
||||
old.buildInputs or [ ]
|
||||
) ++ (
|
||||
builtins.map (
|
||||
pkg: if builtins.isString pkg then builtins.getAttr pkg super else pkg
|
||||
) build-requirements
|
||||
);
|
||||
})
|
||||
) pypkgs-build-requirements
|
||||
);
|
||||
|
||||
# override some ahead-of-time compiled extensions
|
||||
# to be built with their wheels.
|
||||
ahot_overrides = p2n-overrides.extend(
|
||||
final: prev: {
|
||||
|
||||
# llvmlite = prev.llvmlite.override {
|
||||
# preferWheel = false;
|
||||
# };
|
||||
|
||||
# TODO: get this workin with p2n and nixpkgs..
|
||||
# pyqt6 = prev.pyqt6.override {
|
||||
# preferWheel = true;
|
||||
# };
|
||||
|
||||
# NOTE: this DOESN'T work atm but after a fix
|
||||
# to poetry2nix, it will and actually this line
|
||||
# won't be needed - thanks @k900:
|
||||
# https://github.com/nix-community/poetry2nix/pull/1257
|
||||
pyqt5 = prev.pyqt5.override {
|
||||
# withWebkit = false;
|
||||
preferWheel = true;
|
||||
};
|
||||
|
||||
# see PR from @k900:
|
||||
# https://github.com/nix-community/poetry2nix/pull/1257
|
||||
# pyqt5-qt5 = prev.pyqt5-qt5.override {
|
||||
# withWebkit = false;
|
||||
# preferWheel = true;
|
||||
# };
|
||||
|
||||
# TODO: patch in an override for polars to build
|
||||
# from src! See the details likely needed from
|
||||
# the cryptography entry:
|
||||
# https://github.com/nix-community/poetry2nix/blob/master/overrides/default.nix#L426-L435
|
||||
polars = prev.polars.override {
|
||||
preferWheel = true;
|
||||
};
|
||||
}
|
||||
);
|
||||
|
||||
# WHY!? -> output-attrs that `nix develop` scans for:
|
||||
# https://nixos.org/manual/nix/stable/command-ref/new-cli/nix3-develop.html#flake-output-attributes
|
||||
inherit (nixpkgs) lib;
|
||||
forAllSystems = lib.genAttrs lib.systems.flakeExposed;
|
||||
in
|
||||
rec {
|
||||
packages = {
|
||||
# piker = poetry2nix.legacyPackages.x86_64-linux.mkPoetryEditablePackage {
|
||||
# editablePackageSources = { piker = ./piker; };
|
||||
{
|
||||
devShells = forAllSystems (
|
||||
system:
|
||||
let
|
||||
pkgs = nixpkgs.legacyPackages.${system};
|
||||
|
||||
piker = p2npkgs.mkPoetryApplication {
|
||||
projectDir = projectDir;
|
||||
# do store-path extractions
|
||||
qt6baseStorePath = lib.getLib pkgs.qt6.qtbase;
|
||||
# ?TODO? can remove below since manual linking not needed?
|
||||
# qt6QtWaylandStorePath = lib.getLib pkgs.qt6.qtwayland;
|
||||
|
||||
# SEE ABOVE for auto-genned input set, override
|
||||
# buncha deps with extras.. like `setuptools` mostly.
|
||||
# TODO: maybe propose a patch to p2n to show that you
|
||||
# can even do this in the edgecases docs?
|
||||
overrides = ahot_overrides;
|
||||
# XXX NOTE XXX, for now we overlay specific pkgs via
|
||||
# a major-version-pinned-`cpython`
|
||||
cpython = "python313";
|
||||
pypkgs = pkgs."${cpython}Packages";
|
||||
in
|
||||
{
|
||||
default = pkgs.mkShell {
|
||||
|
||||
# XXX: won't work on llvmlite..
|
||||
# preferWheels = true;
|
||||
};
|
||||
};
|
||||
packages = with pkgs; [
|
||||
# XXX, ensure sh completions active!
|
||||
bashInteractive
|
||||
bash-completion
|
||||
|
||||
# devShells.default = pkgs.mkShell {
|
||||
# projectDir = projectDir;
|
||||
# python = "python3.10";
|
||||
# overrides = ahot_overrides;
|
||||
# inputsFrom = [ self.packages.x86_64-linux.piker ];
|
||||
# packages = packages;
|
||||
# # packages = [ poetry2nix.packages.${system}.poetry ];
|
||||
# };
|
||||
# dev utils
|
||||
ruff
|
||||
pypkgs.ruff
|
||||
|
||||
# TODO: grok the difference here..
|
||||
# - avoid re-cloning git repos on every develop entry..
|
||||
# - ideally allow hacking on the src code of some deps
|
||||
# (tractor, pyqtgraph, tomlkit, etc.) WITHOUT having to
|
||||
# re-install them every time a change is made.
|
||||
# - boot a usable xonsh inside the poetry virtualenv when
|
||||
# defined via a custom entry point?
|
||||
devShells.default = p2npkgs.mkPoetryEnv {
|
||||
# env = p2npkgs.mkPoetryEnv {
|
||||
projectDir = projectDir;
|
||||
python = pkgs.python310;
|
||||
overrides = ahot_overrides;
|
||||
editablePackageSources = packages;
|
||||
# piker = "./";
|
||||
# tractor = "../tractor/";
|
||||
# }; # wut?
|
||||
qt6.qtwayland
|
||||
qt6.qtbase
|
||||
|
||||
uv
|
||||
python313 # ?TODO^ how to set from `cpython` above?
|
||||
pypkgs.pyqt6
|
||||
pypkgs.pyqt6-sip
|
||||
pypkgs.qtpy
|
||||
pypkgs.qdarkstyle
|
||||
pypkgs.rapidfuzz
|
||||
];
|
||||
|
||||
shellHook = ''
|
||||
# unmask to debug **this** dev-shell-hook
|
||||
# set -e
|
||||
|
||||
# set qt-base/plugin path(s)
|
||||
QTBASE_PATH="${qt6baseStorePath}/lib"
|
||||
QT_PLUGIN_PATH="${qt6baseStorePath}/lib/qt-6/plugins"
|
||||
QT_QPA_PLATFORM_PLUGIN_PATH="$QT_PLUGIN_PATH/platforms"
|
||||
|
||||
# link in Qt cc lib paths from <nixpkgs>
|
||||
LD_LIBRARY_PATH="$LD_LIBRARY_PATH:$QTBASE_PATH"
|
||||
LD_LIBRARY_PATH="$LD_LIBRARY_PATH:$QT_PLUGIN_PATH"
|
||||
LD_LIBRARY_PATH="$LD_LIBRARY_PATH:$QT_QPA_PLATFORM_PLUGIN_PATH"
|
||||
|
||||
# link-in c++ stdlib for various AOT-ext-pkgs (numpy, etc.)
|
||||
LD_LIBRARY_PATH="${pkgs.stdenv.cc.cc.lib}/lib:$LD_LIBRARY_PATH"
|
||||
|
||||
export LD_LIBRARY_PATH
|
||||
|
||||
# RUNTIME-SETTINGS
|
||||
#
|
||||
# ------ Qt ------
|
||||
# XXX, unmask to debug qt .so linking/loading deats
|
||||
# export QT_DEBUG_PLUGINS=1
|
||||
#
|
||||
# ALSO, for *modern linux* DEs,
|
||||
# - maybe set wayland-mode (TODO, parametrtize this!)
|
||||
# * a chosen wayland-mode shell-integration
|
||||
export QT_QPA_PLATFORM="wayland"
|
||||
export QT_WAYLAND_SHELL_INTEGRATION="xdg-shell"
|
||||
|
||||
# ------ uv ------
|
||||
# - always use the ./py313/ venv-subdir
|
||||
export UV_PROJECT_ENVIRONMENT="py313"
|
||||
# sync project-env with all extras
|
||||
uv sync --dev --all-extras --no-group lint
|
||||
|
||||
# ------ TIPS ------
|
||||
# NOTE, to launch the py-venv installed `xonsh` (like @goodboy)
|
||||
# run the `nix develop` cmd with,
|
||||
# >> nix develop -c uv run xonsh
|
||||
'';
|
||||
};
|
||||
}
|
||||
); # end of .outputs scope
|
||||
);
|
||||
};
|
||||
}
|
||||
|
|
|
|||
|
|
@ -42,7 +42,6 @@ from ._mktinfo import (
|
|||
dec_digits,
|
||||
digits_to_dec,
|
||||
MktPair,
|
||||
Symbol,
|
||||
unpack_fqme,
|
||||
_derivs as DerivTypes,
|
||||
)
|
||||
|
|
@ -60,7 +59,6 @@ __all__ = [
|
|||
'Asset',
|
||||
'MktPair',
|
||||
'Position',
|
||||
'Symbol',
|
||||
'Transaction',
|
||||
'TransactionLedger',
|
||||
'dec_digits',
|
||||
|
|
|
|||
|
|
@ -40,7 +40,7 @@ import tomli_w # for fast ledger writing
|
|||
|
||||
from piker.types import Struct
|
||||
from piker import config
|
||||
from ..log import get_logger
|
||||
from piker.log import get_logger
|
||||
from .calc import (
|
||||
iter_by_dt,
|
||||
)
|
||||
|
|
@ -239,7 +239,9 @@ class TransactionLedger(UserDict):
|
|||
|
||||
symcache: SymbologyCache = self._symcache
|
||||
towrite: dict[str, Any] = {}
|
||||
for tid, txdict in self.tx_sort(self.data.copy()):
|
||||
for tid, txdict in self.tx_sort(
|
||||
self.data.copy()
|
||||
):
|
||||
# write blank-str expiry for non-expiring assets
|
||||
if (
|
||||
'expiry' in txdict
|
||||
|
|
@ -377,7 +379,7 @@ def open_trade_ledger(
|
|||
account,
|
||||
dirpath=_fp,
|
||||
)
|
||||
cpy = ledger_dict.copy()
|
||||
cpy: dict = ledger_dict.copy()
|
||||
|
||||
# XXX NOTE: if not provided presume we are being called from
|
||||
# sync code and need to maybe run `trio` to generate..
|
||||
|
|
@ -406,7 +408,13 @@ def open_trade_ledger(
|
|||
account=account,
|
||||
mod=mod,
|
||||
symcache=symcache,
|
||||
tx_sort=getattr(mod, 'tx_sort', tx_sort),
|
||||
|
||||
# NOTE: allow backends to provide custom ledger sorting
|
||||
tx_sort=getattr(
|
||||
mod,
|
||||
'tx_sort',
|
||||
tx_sort,
|
||||
),
|
||||
)
|
||||
try:
|
||||
yield ledger
|
||||
|
|
|
|||
|
|
@ -305,8 +305,8 @@ class MktPair(Struct, frozen=True):
|
|||
# config right?
|
||||
# src_type: AssetTypeName
|
||||
|
||||
# for derivs, info describing contract, egs.
|
||||
# strike price, call or put, swap type, exercise model, etc.
|
||||
# for derivs, info describing contract, egs. strike price, call
|
||||
# or put, swap type, exercise model, etc.
|
||||
contract_info: list[str] | None = None
|
||||
|
||||
# TODO: rename to sectype since all of these can
|
||||
|
|
@ -677,90 +677,3 @@ def unpack_fqme(
|
|||
# '.'.join([mkt_ep, venue]),
|
||||
suffix,
|
||||
)
|
||||
|
||||
|
||||
class Symbol(Struct):
|
||||
'''
|
||||
I guess this is some kinda container thing for dealing with
|
||||
all the different meta-data formats from brokers?
|
||||
|
||||
'''
|
||||
key: str
|
||||
|
||||
broker: str = ''
|
||||
venue: str = ''
|
||||
|
||||
# precision descriptors for price and vlm
|
||||
tick_size: Decimal = Decimal('0.01')
|
||||
lot_tick_size: Decimal = Decimal('0.0')
|
||||
|
||||
suffix: str = ''
|
||||
broker_info: dict[str, dict[str, Any]] = {}
|
||||
|
||||
@classmethod
|
||||
def from_fqme(
|
||||
cls,
|
||||
fqsn: str,
|
||||
info: dict[str, Any],
|
||||
|
||||
) -> Symbol:
|
||||
broker, mktep, venue, suffix = unpack_fqme(fqsn)
|
||||
tick_size = info.get('price_tick_size', 0.01)
|
||||
lot_size = info.get('lot_tick_size', 0.0)
|
||||
|
||||
return Symbol(
|
||||
broker=broker,
|
||||
key=mktep,
|
||||
tick_size=tick_size,
|
||||
lot_tick_size=lot_size,
|
||||
venue=venue,
|
||||
suffix=suffix,
|
||||
broker_info={broker: info},
|
||||
)
|
||||
|
||||
@property
|
||||
def type_key(self) -> str:
|
||||
return list(self.broker_info.values())[0]['asset_type']
|
||||
|
||||
@property
|
||||
def tick_size_digits(self) -> int:
|
||||
return float_digits(self.tick_size)
|
||||
|
||||
@property
|
||||
def lot_size_digits(self) -> int:
|
||||
return float_digits(self.lot_tick_size)
|
||||
|
||||
@property
|
||||
def price_tick(self) -> Decimal:
|
||||
return Decimal(str(self.tick_size))
|
||||
|
||||
@property
|
||||
def size_tick(self) -> Decimal:
|
||||
return Decimal(str(self.lot_tick_size))
|
||||
|
||||
@property
|
||||
def broker(self) -> str:
|
||||
return list(self.broker_info.keys())[0]
|
||||
|
||||
@property
|
||||
def fqme(self) -> str:
|
||||
return maybe_cons_tokens([
|
||||
self.key, # final "pair name" (eg. qqq[/usd], btcusdt)
|
||||
self.venue,
|
||||
self.suffix, # includes expiry and other con info
|
||||
self.broker,
|
||||
])
|
||||
|
||||
def quantize(
|
||||
self,
|
||||
size: float,
|
||||
) -> Decimal:
|
||||
digits = float_digits(self.lot_tick_size)
|
||||
return Decimal(size).quantize(
|
||||
Decimal(f'1.{"0".ljust(digits, "0")}'),
|
||||
rounding=ROUND_HALF_EVEN
|
||||
)
|
||||
|
||||
# NOTE: when cast to `str` return fqme
|
||||
def __str__(self) -> str:
|
||||
return self.fqme
|
||||
|
|
|
|||
|
|
@ -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.log import get_logger
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from piker.data._symcache import SymbologyCache
|
||||
from ..log import get_logger
|
||||
|
||||
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?
|
||||
|
|
|
|||
|
|
@ -251,10 +251,16 @@ def iter_by_dt(
|
|||
for k in parsers:
|
||||
if (
|
||||
isdict and k in tx
|
||||
or getattr(tx, k, None)
|
||||
or
|
||||
getattr(tx, k, None)
|
||||
):
|
||||
v = tx[k] if isdict else tx.dt
|
||||
assert v is not None, f'No valid value for `{k}`!?'
|
||||
v = (
|
||||
tx[k] if isdict
|
||||
else tx.dt
|
||||
)
|
||||
assert v is not None, (
|
||||
f'No valid value for `{k}`!?'
|
||||
)
|
||||
|
||||
# only call parser on the value if not None from
|
||||
# the `parsers` table above (when NOT using
|
||||
|
|
@ -269,8 +275,21 @@ def iter_by_dt(
|
|||
return v
|
||||
|
||||
else:
|
||||
# XXX: should never get here..
|
||||
breakpoint()
|
||||
# TODO: move to top?
|
||||
from piker.log import get_logger
|
||||
log = get_logger(__name__)
|
||||
|
||||
# XXX: we should really never get here..
|
||||
# only if a ledger record has no expected sort(able)
|
||||
# field will we likely hit this.. like with ze IB.
|
||||
# if no sortable field just deliver epoch?
|
||||
log.warning(
|
||||
'No (time) sortable field for TXN:\n'
|
||||
f'{tx}\n'
|
||||
)
|
||||
return from_timestamp(0)
|
||||
# breakpoint()
|
||||
|
||||
|
||||
entry: tuple[str, dict] | Transaction
|
||||
for entry in sorted(
|
||||
|
|
|
|||
|
|
@ -300,7 +300,8 @@ def disect(
|
|||
assert not df.is_empty()
|
||||
|
||||
# muck around in pdbp REPL
|
||||
breakpoint()
|
||||
# tractor.devx.mk_pdb().set_trace()
|
||||
# breakpoint()
|
||||
|
||||
# TODO: we REALLY need a better console REPL for this
|
||||
# kinda thing..
|
||||
|
|
|
|||
|
|
@ -98,13 +98,14 @@ async def open_cached_client(
|
|||
If one has not been setup do it and cache it.
|
||||
|
||||
'''
|
||||
brokermod = get_brokermod(brokername)
|
||||
brokermod: ModuleType = get_brokermod(brokername)
|
||||
|
||||
# TODO: make abstract or `typing.Protocol`
|
||||
# client: Client
|
||||
async with maybe_open_context(
|
||||
acm_func=brokermod.get_client,
|
||||
kwargs=kwargs,
|
||||
|
||||
) as (cache_hit, client):
|
||||
|
||||
if cache_hit:
|
||||
log.runtime(f'Reusing existing {client}')
|
||||
|
||||
|
|
|
|||
|
|
@ -96,7 +96,10 @@ async def _setup_persistent_brokerd(
|
|||
# - `open_symbol_search()`
|
||||
# NOTE: see ep invocation details inside `.data.feed`.
|
||||
try:
|
||||
async with trio.open_nursery() as service_nursery:
|
||||
async with (
|
||||
tractor.trionics.collapse_eg(),
|
||||
trio.open_nursery() as service_nursery
|
||||
):
|
||||
bus: _FeedsBus = feed.get_feed_bus(
|
||||
brokername,
|
||||
service_nursery,
|
||||
|
|
|
|||
|
|
@ -374,9 +374,14 @@ class Client:
|
|||
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'
|
||||
f'\n'
|
||||
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
|
||||
|
|
|
|||
|
|
@ -97,6 +97,13 @@ class Pair(Struct, frozen=True, kw_only=True):
|
|||
baseAsset: str
|
||||
baseAssetPrecision: int
|
||||
|
||||
permissionSets: list[list[str]]
|
||||
|
||||
# https://developers.binance.com/docs/binance-spot-api-docs#2025-08-26
|
||||
# will become non-optional 2025-08-28?
|
||||
# https://developers.binance.com/docs/binance-spot-api-docs#future-changes
|
||||
pegInstructionsAllowed: bool|None = None
|
||||
|
||||
filters: dict[
|
||||
str,
|
||||
str | int | float,
|
||||
|
|
@ -142,7 +149,11 @@ class SpotPair(Pair, frozen=True):
|
|||
defaultSelfTradePreventionMode: str
|
||||
allowedSelfTradePreventionModes: list[str]
|
||||
permissions: list[str]
|
||||
permissionSets: list[list[str]]
|
||||
|
||||
# can the paint botz creat liq gaps even easier on this asset?
|
||||
# Bp
|
||||
# https://developers.binance.com/docs/binance-spot-api-docs/faqs/order_amend_keep_priority
|
||||
amendAllowed: bool
|
||||
|
||||
# NOTE: see `.data._symcache.SymbologyCache.load()` for why
|
||||
ns_path: str = 'piker.brokers.binance:SpotPair'
|
||||
|
|
|
|||
|
|
@ -471,11 +471,15 @@ def search(
|
|||
|
||||
'''
|
||||
# global opts
|
||||
brokermods = list(config['brokermods'].values())
|
||||
brokermods: list[ModuleType] = list(config['brokermods'].values())
|
||||
|
||||
# TODO: this is coming from the `search --pdb` NOT from
|
||||
# the `piker --pdb` XD ..
|
||||
# -[ ] pull from the parent click ctx's values..dumdum
|
||||
# assert pdb
|
||||
|
||||
# define tractor entrypoint
|
||||
async def main(func):
|
||||
|
||||
async with maybe_open_pikerd(
|
||||
loglevel=config['loglevel'],
|
||||
debug_mode=pdb,
|
||||
|
|
|
|||
|
|
@ -22,7 +22,9 @@ routines should be primitive data types where possible.
|
|||
"""
|
||||
import inspect
|
||||
from types import ModuleType
|
||||
from typing import List, Dict, Any, Optional
|
||||
from typing import (
|
||||
Any,
|
||||
)
|
||||
|
||||
import trio
|
||||
|
||||
|
|
@ -34,8 +36,10 @@ from ..accounting import MktPair
|
|||
|
||||
|
||||
async def api(brokername: str, methname: str, **kwargs) -> dict:
|
||||
"""Make (proxy through) a broker API call by name and return its result.
|
||||
"""
|
||||
'''
|
||||
Make (proxy through) a broker API call by name and return its result.
|
||||
|
||||
'''
|
||||
brokermod = get_brokermod(brokername)
|
||||
async with brokermod.get_client() as client:
|
||||
meth = getattr(client, methname, None)
|
||||
|
|
@ -62,10 +66,14 @@ async def api(brokername: str, methname: str, **kwargs) -> dict:
|
|||
|
||||
async def stocks_quote(
|
||||
brokermod: ModuleType,
|
||||
tickers: List[str]
|
||||
) -> Dict[str, Dict[str, Any]]:
|
||||
"""Return quotes dict for ``tickers``.
|
||||
"""
|
||||
tickers: list[str]
|
||||
|
||||
) -> dict[str, dict[str, Any]]:
|
||||
'''
|
||||
Return a `dict` of snapshot quotes for the provided input
|
||||
`tickers`: a `list` of fqmes.
|
||||
|
||||
'''
|
||||
async with brokermod.get_client() as client:
|
||||
return await client.quote(tickers)
|
||||
|
||||
|
|
@ -74,13 +82,15 @@ async def stocks_quote(
|
|||
async def option_chain(
|
||||
brokermod: ModuleType,
|
||||
symbol: str,
|
||||
date: Optional[str] = None,
|
||||
) -> Dict[str, Dict[str, Dict[str, Any]]]:
|
||||
"""Return option chain for ``symbol`` for ``date``.
|
||||
date: str|None = None,
|
||||
) -> dict[str, dict[str, dict[str, Any]]]:
|
||||
'''
|
||||
Return option chain for ``symbol`` for ``date``.
|
||||
|
||||
By default all expiries are returned. If ``date`` is provided
|
||||
then contract quotes for that single expiry are returned.
|
||||
"""
|
||||
|
||||
'''
|
||||
async with brokermod.get_client() as client:
|
||||
if date:
|
||||
id = int((await client.tickers2ids([symbol]))[symbol])
|
||||
|
|
@ -98,7 +108,7 @@ async def option_chain(
|
|||
# async def contracts(
|
||||
# brokermod: ModuleType,
|
||||
# symbol: str,
|
||||
# ) -> Dict[str, Dict[str, Dict[str, Any]]]:
|
||||
# ) -> dict[str, dict[str, dict[str, Any]]]:
|
||||
# """Return option contracts (all expiries) for ``symbol``.
|
||||
# """
|
||||
# async with brokermod.get_client() as client:
|
||||
|
|
@ -110,15 +120,24 @@ async def bars(
|
|||
brokermod: ModuleType,
|
||||
symbol: str,
|
||||
**kwargs,
|
||||
) -> Dict[str, Dict[str, Dict[str, Any]]]:
|
||||
"""Return option contracts (all expiries) for ``symbol``.
|
||||
"""
|
||||
) -> dict[str, dict[str, dict[str, Any]]]:
|
||||
'''
|
||||
Return option contracts (all expiries) for ``symbol``.
|
||||
|
||||
'''
|
||||
async with brokermod.get_client() as client:
|
||||
return await client.bars(symbol, **kwargs)
|
||||
|
||||
|
||||
async def search_w_brokerd(name: str, pattern: str) -> dict:
|
||||
async def search_w_brokerd(
|
||||
name: str,
|
||||
pattern: str,
|
||||
) -> dict:
|
||||
|
||||
# TODO: WHY NOT WORK!?!
|
||||
# when we `step` through the next block?
|
||||
# import tractor
|
||||
# await tractor.pause()
|
||||
async with open_cached_client(name) as client:
|
||||
|
||||
# TODO: support multiple asset type concurrent searches.
|
||||
|
|
@ -130,12 +149,12 @@ async def symbol_search(
|
|||
pattern: str,
|
||||
**kwargs,
|
||||
|
||||
) -> Dict[str, Dict[str, Dict[str, Any]]]:
|
||||
) -> dict[str, dict[str, dict[str, Any]]]:
|
||||
'''
|
||||
Return symbol info from broker.
|
||||
|
||||
'''
|
||||
results = []
|
||||
results: list[str] = []
|
||||
|
||||
async def search_backend(
|
||||
brokermod: ModuleType
|
||||
|
|
@ -143,6 +162,13 @@ async def symbol_search(
|
|||
|
||||
brokername: str = mod.name
|
||||
|
||||
# TODO: figure this the FUCK OUT
|
||||
# -> ok so obvi in the root actor any async task that's
|
||||
# spawned outside the main tractor-root-actor task needs to
|
||||
# call this..
|
||||
# await tractor.devx._debug.maybe_init_greenback()
|
||||
# tractor.pause_from_sync()
|
||||
|
||||
async with maybe_spawn_brokerd(
|
||||
mod.name,
|
||||
infect_asyncio=getattr(
|
||||
|
|
@ -162,7 +188,6 @@ async def symbol_search(
|
|||
))
|
||||
|
||||
async with trio.open_nursery() as n:
|
||||
|
||||
for mod in brokermods:
|
||||
n.start_soon(search_backend, mod.name)
|
||||
|
||||
|
|
@ -172,11 +197,13 @@ async def symbol_search(
|
|||
async def mkt_info(
|
||||
brokermod: ModuleType,
|
||||
fqme: str,
|
||||
|
||||
**kwargs,
|
||||
|
||||
) -> MktPair:
|
||||
'''
|
||||
Return MktPair info from broker including src and dst assets.
|
||||
Return the `piker.accounting.MktPair` info struct from a given
|
||||
backend broker tradable src/dst asset pair.
|
||||
|
||||
'''
|
||||
async with open_cached_client(brokermod.name) as client:
|
||||
|
|
|
|||
|
|
@ -34,6 +34,7 @@ from piker.brokers._util import get_logger
|
|||
if TYPE_CHECKING:
|
||||
from .api import Client
|
||||
from ib_insync import IB
|
||||
import i3ipc
|
||||
|
||||
log = get_logger('piker.brokers.ib')
|
||||
|
||||
|
|
@ -48,6 +49,37 @@ _reset_tech: Literal[
|
|||
] = 'vnc'
|
||||
|
||||
|
||||
no_setup_msg:str = (
|
||||
'No data reset hack test setup for {vnc_sockaddr}!\n'
|
||||
'See config setup tips @\n'
|
||||
'https://github.com/pikers/piker/tree/master/piker/brokers/ib'
|
||||
)
|
||||
|
||||
|
||||
def try_xdo_manual(
|
||||
vnc_sockaddr: str,
|
||||
):
|
||||
'''
|
||||
Do the "manual" `xdo`-based screen switch + click
|
||||
combo since apparently the `asyncvnc` client ain't workin..
|
||||
|
||||
Note this is only meant as a backup method for Xorg users,
|
||||
ideally you can use a real vnc client and the `vnc_click_hack()`
|
||||
impl!
|
||||
|
||||
'''
|
||||
global _reset_tech
|
||||
try:
|
||||
i3ipc_xdotool_manual_click_hack()
|
||||
_reset_tech = 'i3ipc_xdotool'
|
||||
return True
|
||||
except OSError:
|
||||
log.exception(
|
||||
no_setup_msg.format(vnc_sockaddr)
|
||||
)
|
||||
return False
|
||||
|
||||
|
||||
async def data_reset_hack(
|
||||
# vnc_host: str,
|
||||
client: Client,
|
||||
|
|
@ -90,15 +122,9 @@ async def data_reset_hack(
|
|||
vnc_port: int
|
||||
vnc_sockaddr: tuple[str] | None = client.conf.get('vnc_addrs')
|
||||
|
||||
no_setup_msg:str = (
|
||||
f'No data reset hack test setup for {vnc_sockaddr}!\n'
|
||||
'See config setup tips @\n'
|
||||
'https://github.com/pikers/piker/tree/master/piker/brokers/ib'
|
||||
)
|
||||
|
||||
if not vnc_sockaddr:
|
||||
log.warning(
|
||||
no_setup_msg
|
||||
no_setup_msg.format(vnc_sockaddr)
|
||||
+
|
||||
'REQUIRES A `vnc_addrs: array` ENTRY'
|
||||
)
|
||||
|
|
@ -119,27 +145,38 @@ async def data_reset_hack(
|
|||
port=vnc_port,
|
||||
)
|
||||
)
|
||||
except OSError:
|
||||
if vnc_host != 'localhost':
|
||||
log.warning(no_setup_msg)
|
||||
return False
|
||||
|
||||
except (
|
||||
OSError, # no VNC server avail..
|
||||
PermissionError, # asyncvnc pw fail..
|
||||
):
|
||||
try:
|
||||
import i3ipc # noqa (since a deps dynamic check)
|
||||
except ModuleNotFoundError:
|
||||
log.warning(no_setup_msg)
|
||||
log.warning(
|
||||
no_setup_msg.format(vnc_sockaddr)
|
||||
)
|
||||
return False
|
||||
|
||||
try:
|
||||
i3ipc_xdotool_manual_click_hack()
|
||||
_reset_tech = 'i3ipc_xdotool'
|
||||
return True
|
||||
except OSError:
|
||||
log.exception(no_setup_msg)
|
||||
if vnc_host not in {
|
||||
'localhost',
|
||||
'127.0.0.1',
|
||||
}:
|
||||
focussed, matches = i3ipc_fin_wins_titled()
|
||||
if not matches:
|
||||
log.warning(
|
||||
no_setup_msg.format(vnc_sockaddr)
|
||||
)
|
||||
return False
|
||||
else:
|
||||
try_xdo_manual(vnc_sockaddr)
|
||||
|
||||
# localhost but no vnc-client or it borked..
|
||||
else:
|
||||
try_xdo_manual(vnc_sockaddr)
|
||||
|
||||
case 'i3ipc_xdotool':
|
||||
i3ipc_xdotool_manual_click_hack()
|
||||
try_xdo_manual(vnc_sockaddr)
|
||||
# i3ipc_xdotool_manual_click_hack()
|
||||
|
||||
case _ as tech:
|
||||
raise RuntimeError(f'{tech} is not supported for reset tech!?')
|
||||
|
|
@ -178,9 +215,9 @@ async def vnc_click_hack(
|
|||
host,
|
||||
port=port,
|
||||
|
||||
# TODO: doesn't work see:
|
||||
# https://github.com/barneygale/asyncvnc/issues/7
|
||||
# password='ibcansmbz',
|
||||
# TODO: doesn't work?
|
||||
# see, https://github.com/barneygale/asyncvnc/issues/7
|
||||
password='doggy',
|
||||
|
||||
) as client:
|
||||
|
||||
|
|
@ -194,34 +231,67 @@ async def vnc_click_hack(
|
|||
client.keyboard.press('Ctrl', 'Alt', key) # keys are stacked
|
||||
|
||||
|
||||
def i3ipc_fin_wins_titled(
|
||||
titles: list[str] = [
|
||||
'Interactive Brokers', # tws running in i3
|
||||
'IB Gateway', # gw running in i3
|
||||
# 'IB', # gw running in i3 (newer version?)
|
||||
|
||||
# !TODO, remote vnc instance
|
||||
# -[ ] something in title (or other Con-props) that indicates
|
||||
# this is explicitly for ibrk sw?
|
||||
# |_[ ] !can use modden spawn eventually!
|
||||
'TigerVNC',
|
||||
# 'vncviewer', # the terminal..
|
||||
],
|
||||
) -> tuple[
|
||||
i3ipc.Con, # orig focussed win
|
||||
list[tuple[str, i3ipc.Con]], # matching wins by title
|
||||
]:
|
||||
'''
|
||||
Attempt to find a local-DE window titled with an entry in
|
||||
`titles`.
|
||||
|
||||
If found deliver the current focussed window and all matching
|
||||
`i3ipc.Con`s in a list.
|
||||
|
||||
'''
|
||||
import i3ipc
|
||||
ipc = i3ipc.Connection()
|
||||
|
||||
# TODO: might be worth offering some kinda api for grabbing
|
||||
# the window id from the pid?
|
||||
# https://stackoverflow.com/a/2250879
|
||||
tree = ipc.get_tree()
|
||||
focussed: i3ipc.Con = tree.find_focused()
|
||||
|
||||
matches: list[i3ipc.Con] = []
|
||||
for name in titles:
|
||||
results = tree.find_titled(name)
|
||||
print(f'results for {name}: {results}')
|
||||
if results:
|
||||
con = results[0]
|
||||
matches.append((
|
||||
name,
|
||||
con,
|
||||
))
|
||||
|
||||
return (
|
||||
focussed,
|
||||
matches,
|
||||
)
|
||||
|
||||
|
||||
|
||||
def i3ipc_xdotool_manual_click_hack() -> None:
|
||||
'''
|
||||
Do the data reset hack but expecting a local X-window using `xdotool`.
|
||||
|
||||
'''
|
||||
import i3ipc
|
||||
i3 = i3ipc.Connection()
|
||||
|
||||
# TODO: might be worth offering some kinda api for grabbing
|
||||
# the window id from the pid?
|
||||
# https://stackoverflow.com/a/2250879
|
||||
t = i3.get_tree()
|
||||
|
||||
orig_win_id = t.find_focused().window
|
||||
|
||||
# for tws
|
||||
win_names: list[str] = [
|
||||
'Interactive Brokers', # tws running in i3
|
||||
'IB Gateway', # gw running in i3
|
||||
# 'IB', # gw running in i3 (newer version?)
|
||||
]
|
||||
|
||||
focussed, matches = i3ipc_fin_wins_titled()
|
||||
orig_win_id = focussed.window
|
||||
try:
|
||||
for name in win_names:
|
||||
results = t.find_titled(name)
|
||||
print(f'results for {name}: {results}')
|
||||
if results:
|
||||
con = results[0]
|
||||
for name, con in matches:
|
||||
print(f'Resetting data feed for {name}')
|
||||
win_id = str(con.window)
|
||||
w, h = con.rect.width, con.rect.height
|
||||
|
|
|
|||
|
|
@ -1241,32 +1241,47 @@ async def deliver_trade_events(
|
|||
# never relay errors for non-broker related issues
|
||||
# https://interactivebrokers.github.io/tws-api/message_codes.html
|
||||
code: int = err['error_code']
|
||||
if code in {
|
||||
200, # uhh
|
||||
reason: str = err['reason']
|
||||
reqid: str = str(err['reqid'])
|
||||
|
||||
# "Warning:" msg codes,
|
||||
# https://interactivebrokers.github.io/tws-api/message_codes.html#warning_codes
|
||||
# - 2109: 'Outside Regular Trading Hours'
|
||||
if 'Warning:' in reason:
|
||||
log.warning(
|
||||
f'Order-API-warning: {code!r}\n'
|
||||
f'reqid: {reqid!r}\n'
|
||||
f'\n'
|
||||
f'{pformat(err)}\n'
|
||||
# ^TODO? should we just print the `reason`
|
||||
# not the full `err`-dict?
|
||||
)
|
||||
continue
|
||||
|
||||
# XXX known special (ignore) cases
|
||||
elif code in {
|
||||
200, # uhh.. ni idea
|
||||
|
||||
# hist pacing / connectivity
|
||||
162,
|
||||
165,
|
||||
|
||||
# WARNING codes:
|
||||
# https://interactivebrokers.github.io/tws-api/message_codes.html#warning_codes
|
||||
# Attribute 'Outside Regular Trading Hours' is
|
||||
# " 'ignored based on the order type and
|
||||
# destination. PlaceOrder is now ' 'being
|
||||
# processed.',
|
||||
2109,
|
||||
|
||||
# XXX: lol this isn't even documented..
|
||||
# 'No market data during competing live session'
|
||||
1669,
|
||||
}:
|
||||
log.error(
|
||||
f'Order-API-error which is non-cancel-causing ?!\n'
|
||||
f'\n'
|
||||
f'{pformat(err)}\n'
|
||||
)
|
||||
continue
|
||||
|
||||
reqid: str = str(err['reqid'])
|
||||
reason: str = err['reason']
|
||||
|
||||
if err['reqid'] == -1:
|
||||
log.error(f'TWS external order error:\n{pformat(err)}')
|
||||
log.error(
|
||||
f'TWS external order error ??\n'
|
||||
f'{pformat(err)}\n'
|
||||
)
|
||||
|
||||
flow: dict = dict(
|
||||
flows.get(reqid)
|
||||
|
|
|
|||
|
|
@ -587,7 +587,7 @@ async def get_bars(
|
|||
data_cs.cancel()
|
||||
|
||||
# spawn new data reset task
|
||||
data_cs, reset_done = await nurse.start(
|
||||
data_cs, reset_done = await tn.start(
|
||||
partial(
|
||||
wait_on_data_reset,
|
||||
proxy,
|
||||
|
|
@ -607,11 +607,11 @@ async def get_bars(
|
|||
# such that simultaneous symbol queries don't try data resettingn
|
||||
# too fast..
|
||||
unset_resetter: bool = False
|
||||
async with trio.open_nursery() as nurse:
|
||||
async with trio.open_nursery() as tn:
|
||||
|
||||
# start history request that we allow
|
||||
# to run indefinitely until a result is acquired
|
||||
nurse.start_soon(query)
|
||||
tn.start_soon(query)
|
||||
|
||||
# start history reset loop which waits up to the timeout
|
||||
# for a result before triggering a data feed reset.
|
||||
|
|
@ -631,7 +631,7 @@ async def get_bars(
|
|||
unset_resetter: bool = True
|
||||
|
||||
# spawn new data reset task
|
||||
data_cs, reset_done = await nurse.start(
|
||||
data_cs, reset_done = await tn.start(
|
||||
partial(
|
||||
wait_on_data_reset,
|
||||
proxy,
|
||||
|
|
@ -705,7 +705,9 @@ async def _setup_quote_stream(
|
|||
# to_trio, from_aio = trio.open_memory_channel(2**8) # type: ignore
|
||||
def teardown():
|
||||
ticker.updateEvent.disconnect(push)
|
||||
log.error(f"Disconnected stream for `{symbol}`")
|
||||
log.error(
|
||||
f'Disconnected stream for `{symbol}`'
|
||||
)
|
||||
client.ib.cancelMktData(contract)
|
||||
|
||||
# decouple broadcast mem chan
|
||||
|
|
@ -761,7 +763,10 @@ async def open_aio_quote_stream(
|
|||
symbol: str,
|
||||
contract: Contract | None = None,
|
||||
|
||||
) -> trio.abc.ReceiveStream:
|
||||
) -> (
|
||||
trio.abc.Channel| # iface
|
||||
tractor.to_asyncio.LinkedTaskChannel # actually
|
||||
):
|
||||
|
||||
from tractor.trionics import broadcast_receiver
|
||||
global _quote_streams
|
||||
|
|
@ -778,6 +783,7 @@ async def open_aio_quote_stream(
|
|||
yield from_aio
|
||||
return
|
||||
|
||||
from_aio: tractor.to_asyncio.LinkedTaskChannel
|
||||
async with tractor.to_asyncio.open_channel_from(
|
||||
_setup_quote_stream,
|
||||
symbol=symbol,
|
||||
|
|
@ -983,17 +989,18 @@ async def stream_quotes(
|
|||
)
|
||||
cs: trio.CancelScope | None = None
|
||||
startup: bool = True
|
||||
iter_quotes: trio.abc.Channel
|
||||
while (
|
||||
startup
|
||||
or cs.cancel_called
|
||||
):
|
||||
with trio.CancelScope() as cs:
|
||||
async with (
|
||||
trio.open_nursery() as nurse,
|
||||
trio.open_nursery() as tn,
|
||||
open_aio_quote_stream(
|
||||
symbol=sym,
|
||||
contract=con,
|
||||
) as stream,
|
||||
) as iter_quotes,
|
||||
):
|
||||
# ugh, clear ticks since we've consumed them
|
||||
# (ahem, ib_insync is stateful trash)
|
||||
|
|
@ -1021,9 +1028,9 @@ async def stream_quotes(
|
|||
await rt_ev.wait()
|
||||
cs.cancel() # cancel called should now be set
|
||||
|
||||
nurse.start_soon(reset_on_feed)
|
||||
tn.start_soon(reset_on_feed)
|
||||
|
||||
async with aclosing(stream):
|
||||
async with aclosing(iter_quotes):
|
||||
# if syminfo.get('no_vlm', False):
|
||||
if not init_msg.shm_write_opts['has_vlm']:
|
||||
|
||||
|
|
@ -1038,19 +1045,21 @@ async def stream_quotes(
|
|||
# wait for real volume on feed (trading might be
|
||||
# closed)
|
||||
while True:
|
||||
ticker = await stream.receive()
|
||||
ticker = await iter_quotes.receive()
|
||||
|
||||
# for a real volume contract we rait for
|
||||
# the first "real" trade to take place
|
||||
if (
|
||||
# not calc_price
|
||||
# and not ticker.rtTime
|
||||
not ticker.rtTime
|
||||
False
|
||||
# not ticker.rtTime
|
||||
):
|
||||
# spin consuming tickers until we
|
||||
# get a real market datum
|
||||
log.debug(f"New unsent ticker: {ticker}")
|
||||
continue
|
||||
|
||||
else:
|
||||
log.debug("Received first volume tick")
|
||||
# ugh, clear ticks since we've
|
||||
|
|
@ -1066,13 +1075,18 @@ async def stream_quotes(
|
|||
log.debug(f"First ticker received {quote}")
|
||||
|
||||
# tell data-layer spawner-caller that live
|
||||
# quotes are now streaming.
|
||||
# quotes are now active desptie not having
|
||||
# necessarily received a first vlm/clearing
|
||||
# tick.
|
||||
ticker = await iter_quotes.receive()
|
||||
feed_is_live.set()
|
||||
fqme: str = quote['fqme']
|
||||
await send_chan.send({fqme: quote})
|
||||
|
||||
# last = time.time()
|
||||
async for ticker in stream:
|
||||
async for ticker in iter_quotes:
|
||||
quote = normalize(ticker)
|
||||
fqme = quote['fqme']
|
||||
fqme: str = quote['fqme']
|
||||
await send_chan.send({fqme: quote})
|
||||
|
||||
# ugh, clear ticks since we've consumed them
|
||||
|
|
|
|||
|
|
@ -34,6 +34,7 @@ import urllib.parse
|
|||
import hashlib
|
||||
import hmac
|
||||
import base64
|
||||
import tractor
|
||||
import trio
|
||||
|
||||
from piker import config
|
||||
|
|
@ -372,8 +373,7 @@ class Client:
|
|||
# 1658347714, 'status': 'Success'}]}
|
||||
|
||||
if xfers:
|
||||
import tractor
|
||||
await tractor.pp()
|
||||
await tractor.pause()
|
||||
|
||||
trans: dict[str, Transaction] = {}
|
||||
for entry in xfers:
|
||||
|
|
@ -501,6 +501,7 @@ class Client:
|
|||
for xkey, data in resp['result'].items():
|
||||
|
||||
# NOTE: always cache in pairs tables for faster lookup
|
||||
with tractor.devx.maybe_open_crash_handler(): # as bxerr:
|
||||
pair = Pair(xname=xkey, **data)
|
||||
|
||||
# register the above `Pair` structs for all
|
||||
|
|
|
|||
|
|
@ -175,9 +175,8 @@ async def handle_order_requests(
|
|||
|
||||
case {
|
||||
'account': 'kraken.spot' as account,
|
||||
'action': action,
|
||||
} if action in {'buy', 'sell'}:
|
||||
|
||||
'action': 'buy'|'sell',
|
||||
}:
|
||||
# validate
|
||||
order = BrokerdOrder(**msg)
|
||||
|
||||
|
|
@ -262,6 +261,12 @@ async def handle_order_requests(
|
|||
} | extra
|
||||
|
||||
log.info(f'Submitting WS order request:\n{pformat(req)}')
|
||||
|
||||
# NOTE HOWTO, debug order requests
|
||||
#
|
||||
# if 'XRP' in pair:
|
||||
# await tractor.pause()
|
||||
|
||||
await ws.send_msg(req)
|
||||
|
||||
# placehold for sanity checking in relay loop
|
||||
|
|
@ -544,7 +549,7 @@ async def open_trade_dialog(
|
|||
# to be reloaded.
|
||||
balances: dict[str, float] = await client.get_balances()
|
||||
|
||||
verify_balances(
|
||||
await verify_balances(
|
||||
acnt,
|
||||
src_fiat,
|
||||
balances,
|
||||
|
|
@ -1085,6 +1090,8 @@ async def handle_order_updates(
|
|||
f'Failed to {action} order {reqid}:\n'
|
||||
f'{errmsg}'
|
||||
)
|
||||
# if tractor._state.debug_mode():
|
||||
# await tractor.pause()
|
||||
|
||||
symbol: str = 'N/A'
|
||||
if chain := apiflows.get(reqid):
|
||||
|
|
|
|||
|
|
@ -21,7 +21,6 @@ Symbology defs and search.
|
|||
from decimal import Decimal
|
||||
|
||||
import tractor
|
||||
from rapidfuzz import process as fuzzy
|
||||
|
||||
from piker._cacheables import (
|
||||
async_lifo_cache,
|
||||
|
|
@ -41,8 +40,13 @@ from piker.accounting._mktinfo import (
|
|||
)
|
||||
|
||||
|
||||
# https://www.kraken.com/features/api#get-tradable-pairs
|
||||
class Pair(Struct):
|
||||
'''
|
||||
A tradable asset pair as schema-defined by,
|
||||
|
||||
https://docs.kraken.com/api/docs/rest-api/get-tradable-asset-pairs
|
||||
|
||||
'''
|
||||
xname: str # idiotic bs_mktid equiv i guess?
|
||||
altname: str # alternate pair name
|
||||
wsname: str # WebSocket pair name (if available)
|
||||
|
|
@ -53,7 +57,6 @@ class Pair(Struct):
|
|||
lot: str # volume lot size
|
||||
|
||||
cost_decimals: int
|
||||
costmin: float
|
||||
pair_decimals: int # scaling decimal places for pair
|
||||
lot_decimals: int # scaling decimal places for volume
|
||||
|
||||
|
|
@ -79,6 +82,7 @@ class Pair(Struct):
|
|||
tick_size: float # min price step size
|
||||
status: str
|
||||
|
||||
costmin: str|None = None # XXX, only some mktpairs?
|
||||
short_position_limit: float = 0
|
||||
long_position_limit: float = float('inf')
|
||||
|
||||
|
|
|
|||
|
|
@ -37,6 +37,12 @@ import tractor
|
|||
from async_generator import asynccontextmanager
|
||||
import numpy as np
|
||||
import wrapt
|
||||
|
||||
# TODO, port to `httpx`/`trio-websocket` whenver i get back to
|
||||
# writing a proper ws-api streamer for this backend (since the data
|
||||
# feeds are free now) as per GH feat-req:
|
||||
# https://github.com/pikers/piker/issues/509
|
||||
#
|
||||
import asks
|
||||
|
||||
from ..calc import humanize, percent_change
|
||||
|
|
|
|||
|
|
@ -25,7 +25,10 @@ from typing import TYPE_CHECKING
|
|||
|
||||
import trio
|
||||
import tractor
|
||||
from tractor.trionics import broadcast_receiver
|
||||
from tractor.trionics import (
|
||||
broadcast_receiver,
|
||||
collapse_eg,
|
||||
)
|
||||
|
||||
from ._util import (
|
||||
log, # sub-sys logger
|
||||
|
|
@ -285,8 +288,11 @@ async def open_ems(
|
|||
client._ems_stream = trades_stream
|
||||
|
||||
# start sync code order msg delivery task
|
||||
async with trio.open_nursery() as n:
|
||||
n.start_soon(
|
||||
async with (
|
||||
collapse_eg(),
|
||||
trio.open_nursery() as tn,
|
||||
):
|
||||
tn.start_soon(
|
||||
relay_orders_from_sync_code,
|
||||
client,
|
||||
fqme,
|
||||
|
|
@ -302,4 +308,4 @@ async def open_ems(
|
|||
)
|
||||
|
||||
# stop the sync-msg-relay task on exit.
|
||||
n.cancel_scope.cancel()
|
||||
tn.cancel_scope.cancel()
|
||||
|
|
|
|||
|
|
@ -76,7 +76,6 @@ if TYPE_CHECKING:
|
|||
|
||||
# TODO: numba all of this
|
||||
def mk_check(
|
||||
|
||||
trigger_price: float,
|
||||
known_last: float,
|
||||
action: str,
|
||||
|
|
@ -162,7 +161,7 @@ async def clear_dark_triggers(
|
|||
|
||||
router: Router,
|
||||
brokerd_orders_stream: tractor.MsgStream,
|
||||
quote_stream: tractor.ReceiveMsgStream, # noqa
|
||||
quote_stream: tractor.MsgStream,
|
||||
broker: str,
|
||||
fqme: str,
|
||||
|
||||
|
|
@ -178,6 +177,7 @@ async def clear_dark_triggers(
|
|||
'''
|
||||
# XXX: optimize this for speed!
|
||||
# TODO:
|
||||
# - port to the new ringbuf stuff in `tractor.ipc`!
|
||||
# - numba all this!
|
||||
# - this stream may eventually contain multiple symbols
|
||||
quote_stream._raise_on_lag = False
|
||||
|
|
@ -1190,12 +1190,16 @@ async def process_client_order_cmds(
|
|||
submitting live orders immediately if requested by the client.
|
||||
|
||||
'''
|
||||
# cmd: dict
|
||||
# TODO, only allow `msgspec.Struct` form!
|
||||
cmd: dict
|
||||
async for cmd in client_order_stream:
|
||||
log.info(f'Received order cmd:\n{pformat(cmd)}')
|
||||
log.info(
|
||||
f'Received order cmd:\n'
|
||||
f'{pformat(cmd)}\n'
|
||||
)
|
||||
|
||||
# CAWT DAMN we need struct support!
|
||||
oid = str(cmd['oid'])
|
||||
oid: str = str(cmd['oid'])
|
||||
|
||||
# register this stream as an active order dialog (msg flow) for
|
||||
# this order id such that translated message from the brokerd
|
||||
|
|
@ -1301,7 +1305,7 @@ async def process_client_order_cmds(
|
|||
case {
|
||||
'oid': oid,
|
||||
'symbol': fqme,
|
||||
'price': trigger_price,
|
||||
'price': price,
|
||||
'size': size,
|
||||
'action': ('buy' | 'sell') as action,
|
||||
'exec_mode': ('live' | 'paper'),
|
||||
|
|
@ -1333,7 +1337,7 @@ async def process_client_order_cmds(
|
|||
|
||||
symbol=sym,
|
||||
action=action,
|
||||
price=trigger_price,
|
||||
price=price,
|
||||
size=size,
|
||||
account=req.account,
|
||||
)
|
||||
|
|
@ -1355,7 +1359,11 @@ async def process_client_order_cmds(
|
|||
# (``translate_and_relay_brokerd_events()`` above) will
|
||||
# handle relaying the ems side responses back to
|
||||
# the client/cmd sender from this request
|
||||
log.info(f'Sending live order to {broker}:\n{pformat(msg)}')
|
||||
log.info(
|
||||
f'Sending live order to {broker}:\n'
|
||||
f'{pformat(msg)}'
|
||||
)
|
||||
|
||||
await brokerd_order_stream.send(msg)
|
||||
|
||||
# an immediate response should be ``BrokerdOrderAck``
|
||||
|
|
@ -1371,7 +1379,7 @@ async def process_client_order_cmds(
|
|||
case {
|
||||
'oid': oid,
|
||||
'symbol': fqme,
|
||||
'price': trigger_price,
|
||||
'price': price,
|
||||
'size': size,
|
||||
'exec_mode': exec_mode,
|
||||
'action': action,
|
||||
|
|
@ -1399,7 +1407,12 @@ async def process_client_order_cmds(
|
|||
if isnan(last):
|
||||
last = flume.rt_shm.array[-1]['close']
|
||||
|
||||
pred = mk_check(trigger_price, last, action)
|
||||
trigger_price: float = float(price)
|
||||
pred = mk_check(
|
||||
trigger_price,
|
||||
last,
|
||||
action,
|
||||
)
|
||||
|
||||
# NOTE: for dark orders currently we submit
|
||||
# the triggered live order at a price 5 ticks
|
||||
|
|
|
|||
|
|
@ -19,6 +19,7 @@ Clearing sub-system message and protocols.
|
|||
|
||||
"""
|
||||
from __future__ import annotations
|
||||
from decimal import Decimal
|
||||
from typing import (
|
||||
Literal,
|
||||
)
|
||||
|
|
@ -71,7 +72,15 @@ class Order(Struct):
|
|||
symbol: str # | MktPair
|
||||
account: str # should we set a default as '' ?
|
||||
|
||||
price: float
|
||||
# https://docs.python.org/3/library/decimal.html#decimal-objects
|
||||
#
|
||||
# ?TODO? decimal usage throughout?
|
||||
# -[ ] possibly leverage the `Encoder(decimal_format='number')`
|
||||
# bit?
|
||||
# |_https://jcristharif.com/msgspec/supported-types.html#decimal
|
||||
# -[ ] should we also use it for .size?
|
||||
#
|
||||
price: Decimal
|
||||
size: float # -ve is "sell", +ve is "buy"
|
||||
|
||||
brokers: list[str] = []
|
||||
|
|
@ -178,7 +187,7 @@ class BrokerdOrder(Struct):
|
|||
time_ns: int
|
||||
|
||||
symbol: str # fqme
|
||||
price: float
|
||||
price: Decimal
|
||||
size: float
|
||||
|
||||
# TODO: if we instead rely on a +ve/-ve size to determine
|
||||
|
|
|
|||
|
|
@ -510,7 +510,7 @@ async def handle_order_requests(
|
|||
reqid = await client.submit_limit(
|
||||
oid=order.oid,
|
||||
symbol=f'{order.symbol}.{client.broker}',
|
||||
price=order.price,
|
||||
price=float(order.price),
|
||||
action=order.action,
|
||||
size=order.size,
|
||||
# XXX: by default 0 tells ``ib_insync`` methods that
|
||||
|
|
@ -655,6 +655,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] = []
|
||||
|
|
|
|||
|
|
@ -335,7 +335,7 @@ def services(config, tl, ports):
|
|||
name='service_query',
|
||||
loglevel=config['loglevel'] if tl else None,
|
||||
),
|
||||
tractor.get_arbiter(
|
||||
tractor.get_registry(
|
||||
host=host,
|
||||
port=ports[0]
|
||||
) as portal
|
||||
|
|
|
|||
|
|
@ -740,7 +740,7 @@ async def sample_and_broadcast(
|
|||
|
||||
log.warning(
|
||||
f'Feed OVERRUN {sub_key}'
|
||||
'@{bus.brokername} -> \n'
|
||||
f'@{bus.brokername} -> \n'
|
||||
f'feed @ {chan.uid}\n'
|
||||
f'throttle = {throttle} Hz'
|
||||
)
|
||||
|
|
|
|||
|
|
@ -31,6 +31,7 @@ from pathlib import Path
|
|||
from pprint import pformat
|
||||
from typing import (
|
||||
Any,
|
||||
Callable,
|
||||
Sequence,
|
||||
Hashable,
|
||||
TYPE_CHECKING,
|
||||
|
|
@ -56,7 +57,7 @@ from piker.brokers import (
|
|||
)
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from ..accounting import (
|
||||
from piker.accounting import (
|
||||
Asset,
|
||||
MktPair,
|
||||
)
|
||||
|
|
@ -149,19 +150,36 @@ class SymbologyCache(Struct):
|
|||
'Implement `Client.get_assets()`!'
|
||||
)
|
||||
|
||||
if get_mkt_pairs := getattr(client, 'get_mkt_pairs', None):
|
||||
get_mkt_pairs: Callable|None = getattr(
|
||||
client,
|
||||
'get_mkt_pairs',
|
||||
None,
|
||||
)
|
||||
if not get_mkt_pairs:
|
||||
log.warning(
|
||||
'No symbology cache `Pair` support for `{provider}`..\n'
|
||||
'Implement `Client.get_mkt_pairs()`!'
|
||||
)
|
||||
return self
|
||||
|
||||
pairs: dict[str, Struct] = await get_mkt_pairs()
|
||||
for bs_fqme, pair in pairs.items():
|
||||
if not pairs:
|
||||
log.warning(
|
||||
'No pairs from intial {provider!r} sym-cache request?\n\n'
|
||||
'`Client.get_mkt_pairs()` -> {pairs!r} ?'
|
||||
)
|
||||
return self
|
||||
|
||||
# NOTE: every backend defined pair should
|
||||
# declare it's ns path for roundtrip
|
||||
# serialization lookup.
|
||||
for bs_fqme, pair in pairs.items():
|
||||
if not getattr(pair, 'ns_path', None):
|
||||
# XXX: every backend defined pair must declare
|
||||
# a `.ns_path: tractor.NamespacePath` to enable
|
||||
# roundtrip serialization lookup from a local
|
||||
# cache file.
|
||||
raise TypeError(
|
||||
f'Pair-struct for {self.mod.name} MUST define a '
|
||||
'`.ns_path: str`!\n'
|
||||
f'{pair}'
|
||||
'`.ns_path: str`!\n\n'
|
||||
f'{pair!r}'
|
||||
)
|
||||
|
||||
entry = await self.mod.get_mkt_info(pair.bs_fqme)
|
||||
|
|
@ -195,12 +213,6 @@ class SymbologyCache(Struct):
|
|||
pair,
|
||||
)
|
||||
|
||||
else:
|
||||
log.warning(
|
||||
'No symbology cache `Pair` support for `{provider}`..\n'
|
||||
'Implement `Client.get_mkt_pairs()`!'
|
||||
)
|
||||
|
||||
return self
|
||||
|
||||
@classmethod
|
||||
|
|
|
|||
|
|
@ -39,6 +39,7 @@ from typing import (
|
|||
AsyncContextManager,
|
||||
Awaitable,
|
||||
Sequence,
|
||||
TYPE_CHECKING,
|
||||
)
|
||||
|
||||
import trio
|
||||
|
|
@ -75,6 +76,10 @@ from ._sampling import (
|
|||
uniform_rate_send,
|
||||
)
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from tractor._addr import Address
|
||||
from tractor.msg.types import Aid
|
||||
|
||||
|
||||
class Sub(Struct, frozen=True):
|
||||
'''
|
||||
|
|
@ -786,7 +791,6 @@ async def install_brokerd_search(
|
|||
|
||||
@acm
|
||||
async def maybe_open_feed(
|
||||
|
||||
fqmes: list[str],
|
||||
loglevel: str | None = None,
|
||||
|
||||
|
|
@ -840,7 +844,6 @@ async def maybe_open_feed(
|
|||
|
||||
@acm
|
||||
async def open_feed(
|
||||
|
||||
fqmes: list[str],
|
||||
|
||||
loglevel: str|None = None,
|
||||
|
|
@ -899,19 +902,19 @@ async def open_feed(
|
|||
feed.portals[brokermod] = portal
|
||||
|
||||
# fill out "status info" that the UI can show
|
||||
host, port = portal.channel.raddr
|
||||
if host == '127.0.0.1':
|
||||
host = 'localhost'
|
||||
|
||||
chan: tractor.Channel = portal.chan
|
||||
raddr: Address = chan.raddr
|
||||
aid: Aid = chan.aid
|
||||
# TAG_feed_status_update
|
||||
feed.status.update({
|
||||
'actor_name': portal.channel.uid[0],
|
||||
'host': host,
|
||||
'port': port,
|
||||
'actor_id': aid,
|
||||
'actor_short_id': f'{aid.name}@{aid.pid}',
|
||||
'ipc': chan.raddr.proto_key,
|
||||
'ipc_addr': raddr,
|
||||
'hist_shm': 'NA',
|
||||
'rt_shm': 'NA',
|
||||
'throttle_rate': tick_throttle,
|
||||
'throttle_hz': tick_throttle,
|
||||
})
|
||||
# feed.status.update(init_msg.pop('status', {}))
|
||||
|
||||
# (allocate and) connect to any feed bus for this broker
|
||||
bus_ctxs.append(
|
||||
|
|
|
|||
|
|
@ -36,10 +36,10 @@ from ._sharedmem import (
|
|||
ShmArray,
|
||||
_Token,
|
||||
)
|
||||
from piker.accounting import MktPair
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from ..accounting import MktPair
|
||||
from .feed import Feed
|
||||
from piker.data.feed import Feed
|
||||
|
||||
|
||||
class Flume(Struct):
|
||||
|
|
|
|||
|
|
@ -113,9 +113,9 @@ def validate_backend(
|
|||
)
|
||||
if ep is None:
|
||||
log.warning(
|
||||
f'Provider backend {mod.name} is missing '
|
||||
f'{daemon_name} support :(\n'
|
||||
f'The following endpoint is missing: {name}'
|
||||
f'Provider backend {mod.name!r} is missing '
|
||||
f'{daemon_name!r} support?\n'
|
||||
f'|_module endpoint-func missing: {name!r}\n'
|
||||
)
|
||||
|
||||
inits: list[
|
||||
|
|
|
|||
|
|
@ -498,6 +498,7 @@ async def cascade(
|
|||
|
||||
func_name: str = func.__name__
|
||||
async with (
|
||||
tractor.trionics.collapse_eg(), # avoid multi-taskc tb in console
|
||||
trio.open_nursery() as tn,
|
||||
):
|
||||
# TODO: might be better to just make a "restart" method where
|
||||
|
|
|
|||
|
|
@ -200,7 +200,8 @@ async def open_pikerd(
|
|||
reg_addrs,
|
||||
),
|
||||
tractor.open_nursery() as actor_nursery,
|
||||
trio.open_nursery() as service_nursery,
|
||||
tractor.trionics.collapse_eg(),
|
||||
trio.open_nursery() as service_tn,
|
||||
):
|
||||
for addr in reg_addrs:
|
||||
if addr not in root_actor.accept_addrs:
|
||||
|
|
@ -211,7 +212,7 @@ async def open_pikerd(
|
|||
|
||||
# assign globally for future daemon/task creation
|
||||
Services.actor_n = actor_nursery
|
||||
Services.service_n = service_nursery
|
||||
Services.service_n = service_tn
|
||||
Services.debug_mode = debug_mode
|
||||
|
||||
try:
|
||||
|
|
@ -221,7 +222,7 @@ async def open_pikerd(
|
|||
# TODO: is this more clever/efficient?
|
||||
# if 'samplerd' in Services.service_tasks:
|
||||
# await Services.cancel_service('samplerd')
|
||||
service_nursery.cancel_scope.cancel()
|
||||
service_tn.cancel_scope.cancel()
|
||||
|
||||
|
||||
# TODO: do we even need this?
|
||||
|
|
|
|||
|
|
@ -517,7 +517,7 @@ def with_dts(
|
|||
|
||||
'''
|
||||
return df.with_columns([
|
||||
pl.col(time_col).shift(1).suffix('_prev'),
|
||||
pl.col(time_col).shift(1).name.suffix('_prev'),
|
||||
pl.col(time_col).diff().alias('s_diff'),
|
||||
pl.from_epoch(pl.col(time_col)).alias('dt'),
|
||||
]).with_columns([
|
||||
|
|
@ -623,7 +623,7 @@ def detect_vlm_gaps(
|
|||
|
||||
) -> pl.DataFrame:
|
||||
|
||||
vnull: pl.DataFrame = w_dts.filter(
|
||||
vnull: pl.DataFrame = df.filter(
|
||||
pl.col(col) == 0
|
||||
)
|
||||
return vnull
|
||||
|
|
|
|||
|
|
@ -21,6 +21,7 @@ Main app startup and run.
|
|||
from functools import partial
|
||||
from types import ModuleType
|
||||
|
||||
import tractor
|
||||
import trio
|
||||
|
||||
from piker.ui.qt import (
|
||||
|
|
@ -116,6 +117,7 @@ async def _async_main(
|
|||
needed_brokermods[brokername] = brokers[brokername]
|
||||
|
||||
async with (
|
||||
tractor.trionics.collapse_eg(),
|
||||
trio.open_nursery() as root_n,
|
||||
):
|
||||
# set root nursery and task stack for spawning other charts/feeds
|
||||
|
|
|
|||
|
|
@ -33,7 +33,6 @@ import trio
|
|||
|
||||
from piker.ui.qt import (
|
||||
QtCore,
|
||||
QtWidgets,
|
||||
Qt,
|
||||
QLineF,
|
||||
QFrame,
|
||||
|
|
|
|||
|
|
@ -1445,7 +1445,10 @@ async def display_symbol_data(
|
|||
# for pause/resume on mouse interaction
|
||||
rt_chart.feed = feed
|
||||
|
||||
async with trio.open_nursery() as ln:
|
||||
async with (
|
||||
tractor.trionics.collapse_eg(),
|
||||
trio.open_nursery() as ln,
|
||||
):
|
||||
# if available load volume related built-in display(s)
|
||||
vlm_charts: dict[
|
||||
str,
|
||||
|
|
|
|||
|
|
@ -22,7 +22,10 @@ from contextlib import asynccontextmanager as acm
|
|||
from typing import Callable
|
||||
|
||||
import trio
|
||||
from tractor.trionics import gather_contexts
|
||||
from tractor.trionics import (
|
||||
gather_contexts,
|
||||
collapse_eg,
|
||||
)
|
||||
|
||||
from piker.ui.qt import (
|
||||
QtCore,
|
||||
|
|
@ -207,7 +210,10 @@ async def open_signal_handler(
|
|||
async for args in recv:
|
||||
await async_handler(*args)
|
||||
|
||||
async with trio.open_nursery() as tn:
|
||||
async with (
|
||||
collapse_eg(),
|
||||
trio.open_nursery() as tn
|
||||
):
|
||||
tn.start_soon(proxy_to_handler)
|
||||
async with send:
|
||||
yield
|
||||
|
|
@ -242,6 +248,7 @@ async def open_handlers(
|
|||
widget: QWidget
|
||||
streams: list[trio.abc.ReceiveChannel]
|
||||
async with (
|
||||
collapse_eg(),
|
||||
trio.open_nursery() as tn,
|
||||
gather_contexts([
|
||||
open_event_stream(
|
||||
|
|
|
|||
|
|
@ -18,10 +18,11 @@
|
|||
Feed status and controls widget(s) for embedding in a UI-pane.
|
||||
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
from textwrap import dedent
|
||||
from typing import TYPE_CHECKING
|
||||
from typing import (
|
||||
Any,
|
||||
TYPE_CHECKING,
|
||||
)
|
||||
|
||||
# from PyQt5.QtCore import Qt
|
||||
|
||||
|
|
@ -49,35 +50,55 @@ def mk_feed_label(
|
|||
a feed control protocol.
|
||||
|
||||
'''
|
||||
status = feed.status
|
||||
status: dict[str, Any] = feed.status
|
||||
assert status
|
||||
|
||||
msg = dedent("""
|
||||
actor: **{actor_name}**\n
|
||||
|_ @**{host}:{port}**\n
|
||||
""")
|
||||
# SO tips on ws/nls,
|
||||
# https://stackoverflow.com/a/15721400
|
||||
ws: str = ' '
|
||||
# nl: str = '<br>' # dun work?
|
||||
actor_info_repr: str = (
|
||||
f')> **{status["actor_short_id"]}**\n'
|
||||
'\n' # bc md?
|
||||
)
|
||||
|
||||
for key, val in status.items():
|
||||
if key in ('host', 'port', 'actor_name'):
|
||||
continue
|
||||
msg += f'\n|_ {key}: **{{{key}}}**\n'
|
||||
# fields to select *IN* for display
|
||||
# (see `.data.feed.open_feed()` status
|
||||
# update -> TAG_feed_status_update)
|
||||
for key in [
|
||||
'ipc',
|
||||
'hist_shm',
|
||||
'rt_shm',
|
||||
'throttle_hz',
|
||||
]:
|
||||
# NOTE, the 2nd key is filled via `.format()` updates.
|
||||
actor_info_repr += (
|
||||
f'\n' # bc md?
|
||||
f'{ws}|_{key}: **{{{key}}}**\n'
|
||||
)
|
||||
# ^TODO? formatting and content..
|
||||
# -[ ] showing which fqme is "forward" on the
|
||||
# chart/fsp/order-mode?
|
||||
# '|_ flows: **{symbols}**\n'
|
||||
#
|
||||
# -[x] why isn't the indent working?
|
||||
# => markdown, now solved..
|
||||
|
||||
feed_label = FormatLabel(
|
||||
fmt_str=msg,
|
||||
# |_ streams: **{symbols}**\n
|
||||
fmt_str=actor_info_repr,
|
||||
font=_font.font,
|
||||
font_size=_font_small.px_size,
|
||||
font_color='default_lightest',
|
||||
)
|
||||
|
||||
# ?TODO, remove this?
|
||||
# form.vbox.setAlignment(feed_label, Qt.AlignBottom)
|
||||
# form.vbox.setAlignment(Qt.AlignBottom)
|
||||
_ = chart.height() - (
|
||||
form.height() +
|
||||
form.fill_bar.height()
|
||||
# feed_label.height()
|
||||
)
|
||||
# _ = chart.height() - (
|
||||
# form.height() +
|
||||
# form.fill_bar.height()
|
||||
# # feed_label.height()
|
||||
# )
|
||||
|
||||
feed_label.format(**feed.status)
|
||||
|
||||
return feed_label
|
||||
|
|
|
|||
|
|
@ -600,6 +600,7 @@ async def open_fsp_admin(
|
|||
kwargs=kwargs,
|
||||
) as (cache_hit, cluster_map),
|
||||
|
||||
tractor.trionics.collapse_eg(),
|
||||
trio.open_nursery() as tn,
|
||||
):
|
||||
if cache_hit:
|
||||
|
|
@ -613,6 +614,8 @@ async def open_fsp_admin(
|
|||
)
|
||||
try:
|
||||
yield admin
|
||||
|
||||
# ??TODO, does this *need* to be inside a finally?
|
||||
finally:
|
||||
# terminate all tasks via signals
|
||||
for key, entry in admin._registry.items():
|
||||
|
|
|
|||
|
|
@ -285,18 +285,20 @@ class FormatLabel(QLabel):
|
|||
font_size: int,
|
||||
font_color: str,
|
||||
|
||||
use_md: bool = True,
|
||||
|
||||
parent=None,
|
||||
|
||||
) -> None:
|
||||
|
||||
super().__init__(parent)
|
||||
|
||||
# by default set the format string verbatim and expect user to
|
||||
# call ``.format()`` later (presumably they'll notice the
|
||||
# by default set the format string verbatim and expect user
|
||||
# to call ``.format()`` later (presumably they'll notice the
|
||||
# unformatted content if ``fmt_str`` isn't meant to be
|
||||
# unformatted).
|
||||
self.fmt_str = fmt_str
|
||||
self.setText(fmt_str)
|
||||
# self.setText(fmt_str) # ?TODO, why here?
|
||||
|
||||
self.setStyleSheet(
|
||||
f"""QLabel {{
|
||||
|
|
@ -306,6 +308,7 @@ class FormatLabel(QLabel):
|
|||
"""
|
||||
)
|
||||
self.setFont(_font.font)
|
||||
if use_md:
|
||||
self.setTextFormat(
|
||||
Qt.TextFormat.MarkdownText
|
||||
)
|
||||
|
|
@ -316,7 +319,10 @@ class FormatLabel(QLabel):
|
|||
size_policy.Expanding,
|
||||
)
|
||||
self.setAlignment(
|
||||
Qt.AlignVCenter | Qt.AlignLeft
|
||||
Qt.AlignLeft
|
||||
|
|
||||
Qt.AlignBottom
|
||||
# Qt.AlignVCenter
|
||||
)
|
||||
self.setText(self.fmt_str)
|
||||
|
||||
|
|
|
|||
|
|
@ -269,6 +269,8 @@ def hcolor(name: str) -> str:
|
|||
|
||||
# default ohlc-bars/curve gray
|
||||
'bracket': '#666666', # like the logo
|
||||
'pikers': '#616161', # a trader shade of..
|
||||
'beast': '#161616', # in the dark alone.
|
||||
|
||||
# bluish
|
||||
'charcoal': '#36454F',
|
||||
|
|
|
|||
|
|
@ -21,6 +21,7 @@ Chart trading, the only way to scalp.
|
|||
from __future__ import annotations
|
||||
from contextlib import asynccontextmanager
|
||||
from dataclasses import dataclass, field
|
||||
from decimal import Decimal
|
||||
from functools import partial
|
||||
from pprint import pformat
|
||||
import time
|
||||
|
|
@ -41,7 +42,6 @@ from piker.accounting import (
|
|||
Position,
|
||||
mk_allocator,
|
||||
MktPair,
|
||||
Symbol,
|
||||
)
|
||||
from piker.clearing import (
|
||||
open_ems,
|
||||
|
|
@ -143,6 +143,15 @@ class OrderMode:
|
|||
}
|
||||
_staged_order: Order | None = None
|
||||
|
||||
@property
|
||||
def curr_mkt(self) -> MktPair:
|
||||
'''
|
||||
Deliver the currently selected `MktPair` according
|
||||
chart state.
|
||||
|
||||
'''
|
||||
return self.chart.linked.mkt
|
||||
|
||||
def on_level_change_update_next_order_info(
|
||||
self,
|
||||
level: float,
|
||||
|
|
@ -172,7 +181,11 @@ class OrderMode:
|
|||
line.update_labels(order_info)
|
||||
|
||||
# update bound-in staged order
|
||||
order.price = level
|
||||
mkt: MktPair = self.curr_mkt
|
||||
order.price: Decimal = mkt.quantize(
|
||||
size=level,
|
||||
quantity_type='price',
|
||||
)
|
||||
order.size = order_info['size']
|
||||
|
||||
# when an order is changed we flip the settings side-pane to
|
||||
|
|
@ -187,7 +200,9 @@ class OrderMode:
|
|||
|
||||
) -> LevelLine:
|
||||
|
||||
level = order.price
|
||||
# TODO, if we instead just always decimalize at the ems layer
|
||||
# we can avoid this back-n-forth casting?
|
||||
level = float(order.price)
|
||||
|
||||
line = order_line(
|
||||
chart or self.chart,
|
||||
|
|
@ -224,7 +239,11 @@ class OrderMode:
|
|||
# the order mode allocator but we still need to update the
|
||||
# "staged" order message we'll send to the ems
|
||||
def update_order_price(y: float) -> None:
|
||||
order.price = y
|
||||
mkt: MktPair = self.curr_mkt
|
||||
order.price: Decimal = mkt.quantize(
|
||||
size=y,
|
||||
quantity_type='price',
|
||||
)
|
||||
|
||||
line._on_level_change = update_order_price
|
||||
|
||||
|
|
@ -275,34 +294,31 @@ class OrderMode:
|
|||
chart = cursor.linked.chart
|
||||
if (
|
||||
not chart
|
||||
and cursor
|
||||
and cursor.active_plot
|
||||
and
|
||||
cursor
|
||||
and
|
||||
cursor.active_plot
|
||||
):
|
||||
return
|
||||
|
||||
chart = cursor.active_plot
|
||||
price = cursor._datum_xy[1]
|
||||
price: float = cursor._datum_xy[1]
|
||||
if not price:
|
||||
# zero prices are not supported by any means
|
||||
# since that's illogical / a no-op.
|
||||
return
|
||||
|
||||
mkt: MktPair = self.chart.linked.mkt
|
||||
|
||||
# NOTE : we could also use instead,
|
||||
# mkt.quantize(price, quantity_type='price')
|
||||
# but it returns a Decimal and it's probably gonna
|
||||
# be slower?
|
||||
# TODO: should we be enforcing this precision
|
||||
# at a different layer in the stack? right now
|
||||
# any precision error will literally be relayed
|
||||
# all the way back from the backend.
|
||||
|
||||
price = round(
|
||||
price,
|
||||
ndigits=mkt.price_tick_digits,
|
||||
# at a different layer in the stack?
|
||||
# |_ might require `MktPair` tracking in the EMS?
|
||||
# |_ right now any precision error will be relayed
|
||||
# all the way back from the backend and vice-versa..
|
||||
#
|
||||
mkt: MktPair = self.curr_mkt
|
||||
price: Decimal = mkt.quantize(
|
||||
size=price,
|
||||
quantity_type='price',
|
||||
)
|
||||
|
||||
order = self._staged_order = Order(
|
||||
action=action,
|
||||
price=price,
|
||||
|
|
@ -378,7 +394,7 @@ class OrderMode:
|
|||
'oid': oid,
|
||||
})
|
||||
|
||||
if order.price <= 0:
|
||||
if float(order.price) <= 0:
|
||||
log.error(
|
||||
'*!? Invalid `Order.price <= 0` ?!*\n'
|
||||
# TODO: make this present multi-line in object form
|
||||
|
|
@ -515,14 +531,15 @@ class OrderMode:
|
|||
# if an order msg is provided update the line
|
||||
# **from** that msg.
|
||||
if order:
|
||||
if order.price <= 0:
|
||||
price: float = float(order.price)
|
||||
if price <= 0:
|
||||
log.error(f'Order has 0 price, cancelling..\n{order}')
|
||||
self.cancel_orders([order.oid])
|
||||
return None
|
||||
|
||||
line.set_level(order.price)
|
||||
line.set_level(price)
|
||||
self.on_level_change_update_next_order_info(
|
||||
level=order.price,
|
||||
level=price,
|
||||
line=line,
|
||||
order=order,
|
||||
# use the corresponding position tracker for the
|
||||
|
|
@ -681,9 +698,9 @@ class OrderMode:
|
|||
) -> Dialog | None:
|
||||
# NOTE: the `.order` attr **must** be set with the
|
||||
# equivalent order msg in order to be loaded.
|
||||
order = msg.req
|
||||
order: Order = msg.req
|
||||
oid = str(msg.oid)
|
||||
symbol = order.symbol
|
||||
symbol: str = order.symbol
|
||||
|
||||
# TODO: MEGA UGGG ZONEEEE!
|
||||
src = msg.src
|
||||
|
|
@ -702,13 +719,22 @@ class OrderMode:
|
|||
order.oid = str(order.oid)
|
||||
order.brokers = [brokername]
|
||||
|
||||
# TODO: change this over to `MktPair`, but it's
|
||||
# gonna be tough since we don't have any such data
|
||||
# really in our clearing msg schema..
|
||||
order.symbol = Symbol.from_fqme(
|
||||
fqsn=fqme,
|
||||
info={},
|
||||
)
|
||||
# ?TODO? change this over to `MktPair`, but it's gonna be
|
||||
# tough since we don't have any such data really in our
|
||||
# clearing msg schema..
|
||||
# BUT WAIT! WHY do we even want/need this!?
|
||||
#
|
||||
# order.symbol = self.curr_mkt
|
||||
#
|
||||
# XXX, the old approach.. which i don't quire member why..
|
||||
# -[ ] verify we for sure don't require this any more!
|
||||
# |_https://github.com/pikers/piker/issues/517
|
||||
#
|
||||
# order.symbol = Symbol.from_fqme(
|
||||
# fqsn=fqme,
|
||||
# info={},
|
||||
# )
|
||||
|
||||
maybe_dialog: Dialog | None = self.submit_order(
|
||||
send_msg=False,
|
||||
order=order,
|
||||
|
|
@ -766,6 +792,7 @@ async def open_order_mode(
|
|||
brokerd_accounts,
|
||||
ems_dialog_msgs,
|
||||
),
|
||||
tractor.trionics.collapse_eg(),
|
||||
trio.open_nursery() as tn,
|
||||
|
||||
):
|
||||
|
|
@ -1101,7 +1128,7 @@ async def process_trade_msg(
|
|||
)
|
||||
)
|
||||
):
|
||||
msg.req = order
|
||||
msg.req: Order = order
|
||||
dialog: (
|
||||
Dialog
|
||||
# NOTE: on an invalid order submission (eg.
|
||||
|
|
@ -1166,7 +1193,7 @@ async def process_trade_msg(
|
|||
tm = time.time()
|
||||
mode.on_fill(
|
||||
oid,
|
||||
price=req.price,
|
||||
price=float(req.price),
|
||||
time_s=tm,
|
||||
)
|
||||
mode.lines.remove_line(uuid=oid)
|
||||
|
|
@ -1221,7 +1248,7 @@ async def process_trade_msg(
|
|||
tm = details['broker_time']
|
||||
mode.on_fill(
|
||||
oid,
|
||||
price=details['price'],
|
||||
price=float(details['price']),
|
||||
time_s=tm,
|
||||
pointing='up' if action == 'buy' else 'down',
|
||||
)
|
||||
|
|
|
|||
124
pyproject.toml
124
pyproject.toml
|
|
@ -23,7 +23,7 @@ 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"
|
||||
requires-python = ">=3.12"
|
||||
license = "AGPL-3.0-or-later"
|
||||
readme = "README.rst"
|
||||
keywords = [
|
||||
|
|
@ -39,8 +39,8 @@ classifiers = [
|
|||
"Operating System :: POSIX :: Linux",
|
||||
"Programming Language :: Python :: Implementation :: CPython",
|
||||
"Programming Language :: Python :: 3 :: Only",
|
||||
"Programming Language :: Python :: 3.11",
|
||||
"Programming Language :: Python :: 3.12",
|
||||
"Programming Language :: Python :: 3.13",
|
||||
"Intended Audience :: Financial and Insurance Industry",
|
||||
"Intended Audience :: Science/Research",
|
||||
"Intended Audience :: Developers",
|
||||
|
|
@ -49,13 +49,13 @@ classifiers = [
|
|||
dependencies = [
|
||||
"async-generator >=1.10, <2.0.0",
|
||||
"attrs >=23.1.0, <24.0.0",
|
||||
"bidict >=0.22.1, <0.23.0",
|
||||
"bidict >=0.23.1",
|
||||
"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",
|
||||
"numpy>=2.0",
|
||||
"polars >=0.20.6",
|
||||
"polars-fuzzy-match>=0.1.5",
|
||||
"pygments >=2.16.1, <3.0.0",
|
||||
"rich >=13.5.2, <14.0.0",
|
||||
"tomli >=2.0.1, <3.0.0",
|
||||
|
|
@ -63,21 +63,27 @@ dependencies = [
|
|||
"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",
|
||||
"trio >=0.27",
|
||||
"pendulum",
|
||||
"httpx >=0.27.0, <0.28.0",
|
||||
"cryptofeed >=2.4.0, <3.0.0",
|
||||
"pyarrow >=17.0.0, <18.0.0",
|
||||
"pyarrow>=18.0.0",
|
||||
"websockets ==12.0",
|
||||
"msgspec",
|
||||
"msgspec>=0.19.0,<0.20",
|
||||
"tractor",
|
||||
"asyncvnc",
|
||||
"tomlkit",
|
||||
"trio-typing>=0.10.0",
|
||||
"numba>=0.61.0",
|
||||
"pyvnc",
|
||||
]
|
||||
# ------ dependencies ------
|
||||
|
||||
[project.optional-dependencies]
|
||||
|
||||
# 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
|
||||
|
||||
[dependency-groups]
|
||||
uis = [
|
||||
# https://docs.astral.sh/uv/concepts/projects/dependencies/#optional-dependencies
|
||||
# TODO: make sure the levenshtein shit compiles on nix..
|
||||
|
|
@ -90,12 +96,11 @@ uis = [
|
|||
# for consideration,
|
||||
# - 'visidata'
|
||||
|
||||
# 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
|
||||
"qdarkstyle >=3.0.2, <4.0.0",
|
||||
"pyqt6 >=6.7.0, <7.0.0",
|
||||
"pyqtgraph",
|
||||
]
|
||||
|
||||
[dependency-groups]
|
||||
# TODO: a toolset that makes debugging a `pikerd` service (tree) easy
|
||||
# to hack on directly using more or less the local env:
|
||||
# - xonsh + xxh
|
||||
|
|
@ -104,31 +109,92 @@ uis = [
|
|||
#
|
||||
# console ehancements and eventually remote debugging extras/helpers.
|
||||
# use `uv --dev` to enable
|
||||
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",
|
||||
repl = [
|
||||
# debug
|
||||
"pdbp >=1.5.0, <2.0.0",
|
||||
"greenback >=1.1.1, <2.0.0",
|
||||
"ruff>=0.9.6",
|
||||
"xonsh",
|
||||
"prompt-toolkit ==3.0.40",
|
||||
"pyperclip>=1.9.0",
|
||||
|
||||
]
|
||||
testing = [
|
||||
"pytest",
|
||||
]
|
||||
de = [
|
||||
# DE-specific
|
||||
"i3ipc>=2.2.1",
|
||||
]
|
||||
dev = [
|
||||
# https://docs.astral.sh/uv/concepts/projects/dependencies/#development-dependencies
|
||||
"cython >=3.0.0, <4.0.0",
|
||||
|
||||
# nested deps-groups
|
||||
# https://docs.astral.sh/uv/concepts/projects/dependencies/#nesting-groups
|
||||
{include-group = 'uis'},
|
||||
{include-group = 'repl'},
|
||||
{include-group = 'testing'},
|
||||
{include-group = 'de'},
|
||||
]
|
||||
lint = [
|
||||
# XXX, with flake.nix needs to be from nixpkgs
|
||||
"ruff>=0.9.6"
|
||||
#
|
||||
# ^TODO? these markers don't work; use deps-flags for now?
|
||||
# ; os_name != 'nixos' and platform_system != 'NixOS'",
|
||||
# ; defined('IN_NIX_SHELL')",
|
||||
]
|
||||
dbs = [
|
||||
"elasticsearch >=8.9.0, <9.0.0",
|
||||
]
|
||||
# ------ dependency-groups ------
|
||||
|
||||
|
||||
[tool.pytest.ini_options]
|
||||
# https://docs.pytest.org/en/stable/reference/reference.html#configuration-options
|
||||
testpaths = [
|
||||
"tests",
|
||||
]
|
||||
# https://docs.pytest.org/en/stable/reference/reference.html#confval-console_output_style
|
||||
console_output_style = 'progress'
|
||||
|
||||
# https://docs.pytest.org/en/stable/how-to/plugins.html#disabling-plugins-from-autoloading
|
||||
# https://docs.pytest.org/en/stable/how-to/plugins.html#deactivating-unregistering-a-plugin-by-name
|
||||
addopts = '-p no:xonsh'
|
||||
# ------ tool.pytest ------
|
||||
|
||||
|
||||
[project.scripts]
|
||||
piker = "piker.cli:cli"
|
||||
pikerd = "piker.cli:pikerd"
|
||||
ledger = "piker.accounting.cli:ledger"
|
||||
# ------ project.scripts ------
|
||||
|
||||
[tool.hatch.build.targets.sdist]
|
||||
include = ["piker"]
|
||||
|
||||
[tool.hatch.build.targets.wheel]
|
||||
include = ["piker"]
|
||||
# ------ tool.hatch ------
|
||||
|
||||
|
||||
# TODO? move to a `uv.toml`?
|
||||
[tool.uv]
|
||||
python-preference = 'system'
|
||||
python-downloads = 'manual'
|
||||
# https://docs.astral.sh/uv/concepts/projects/dependencies/#default-groups
|
||||
default-groups = ['uis', 'dev']
|
||||
# ------ tool.uv ------
|
||||
|
||||
|
||||
[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 = { git = "https://pikers.dev/goodboy/tractor", branch = "piker_pin" }
|
||||
pyvnc = { git = "https://github.com/regulad/pyvnc.git" }
|
||||
|
||||
# XXX since, we're like, always hacking new shite all-the-time. Bp
|
||||
tractor = { git = "https://github.com/goodboy/tractor.git", branch ="piker_pin" }
|
||||
# tractor = { git = "https://pikers.dev/goodboy/tractor", branch = "piker_pin" }
|
||||
# tractor = { git = "https://pikers.dev/goodboy/tractor", branch = "main" }
|
||||
# ------ goodboy ------
|
||||
# hackin dev-envs, usually there's something new he's hackin in..
|
||||
# tractor = { path = "../tractor", editable = true }
|
||||
|
|
|
|||
|
|
@ -62,8 +62,9 @@ ignore-init-module-imports = false
|
|||
fixable = ["ALL"]
|
||||
unfixable = []
|
||||
|
||||
# TODO? uhh why no work!?
|
||||
# Allow unused variables when underscore-prefixed.
|
||||
dummy-variable-rgx = "^(_+|(_+[a-zA-Z0-9_]*[a-zA-Z0-9]+?))$"
|
||||
# dummy-variable-rgx = "^(_+|(_+[a-zA-Z0-9_]*[a-zA-Z0-9]+?))$"
|
||||
|
||||
[format]
|
||||
# Use single quotes in `ruff format`.
|
||||
|
|
|
|||
|
|
@ -0,0 +1 @@
|
|||
TAG_feed_status_update ./piker/data/feed.py /TAG_feed_status_update/
|
||||
|
|
@ -179,7 +179,7 @@ def test_ems_err_on_bad_broker(
|
|||
# NOTE: emsd should error on the actor's enabled modules
|
||||
# import phase, when looking for a backend named `doggy`.
|
||||
except tractor.RemoteActorError as re:
|
||||
assert re.type == ModuleNotFoundError
|
||||
assert re.type is ModuleNotFoundError
|
||||
|
||||
run_and_tollerate_cancels(load_bad_fqme)
|
||||
|
||||
|
|
|
|||
Loading…
Reference in New Issue