Compare commits
7 Commits
eb51033b18
...
d649749e7d
Author | SHA1 | Date |
---|---|---|
Tyler Goodlet | d649749e7d | |
Tyler Goodlet | 0f747d8d87 | |
Tyler Goodlet | 4a3c14916d | |
Tyler Goodlet | fc848ef34f | |
Tyler Goodlet | e824572d7c | |
Tyler Goodlet | 275704235f | |
Tyler Goodlet | de655bfe6a |
|
@ -0,0 +1,92 @@
|
||||||
|
# piker: trading gear for hackers
|
||||||
|
# Copyright (C) Tyler Goodlet (in stewardship for pikers)
|
||||||
|
|
||||||
|
# This program is free software: you can redistribute it and/or modify
|
||||||
|
# it under the terms of the GNU Affero General Public License as published by
|
||||||
|
# the Free Software Foundation, either version 3 of the License, or
|
||||||
|
# (at your option) any later version.
|
||||||
|
|
||||||
|
# This program is distributed in the hope that it will be useful,
|
||||||
|
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
# GNU Affero General Public License for more details.
|
||||||
|
|
||||||
|
# You should have received a copy of the GNU Affero General Public License
|
||||||
|
|
||||||
|
# along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
'''
|
||||||
|
"Accounting for degens": count dem numberz that tracks how much you got
|
||||||
|
for tendiez.
|
||||||
|
|
||||||
|
'''
|
||||||
|
from ..log import get_logger
|
||||||
|
|
||||||
|
from ._pos import (
|
||||||
|
Transaction,
|
||||||
|
open_trade_ledger,
|
||||||
|
PpTable,
|
||||||
|
)
|
||||||
|
from ._pos import (
|
||||||
|
open_pps,
|
||||||
|
load_pps_from_ledger,
|
||||||
|
Position,
|
||||||
|
)
|
||||||
|
|
||||||
|
log = get_logger(__name__)
|
||||||
|
|
||||||
|
__all__ = [
|
||||||
|
'Transaction',
|
||||||
|
'open_trade_ledger',
|
||||||
|
'PpTable',
|
||||||
|
'open_pps',
|
||||||
|
'load_pps_from_ledger',
|
||||||
|
'Position',
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
|
def get_likely_pair(
|
||||||
|
src: str,
|
||||||
|
dst: str,
|
||||||
|
bsuid: str,
|
||||||
|
|
||||||
|
) -> str:
|
||||||
|
'''
|
||||||
|
Attempt to get the likely trading pair matching a given destination
|
||||||
|
asset `dst: str`.
|
||||||
|
|
||||||
|
'''
|
||||||
|
try:
|
||||||
|
src_name_start = bsuid.rindex(src)
|
||||||
|
except (
|
||||||
|
ValueError, # substr not found
|
||||||
|
):
|
||||||
|
# TODO: handle nested positions..(i.e.
|
||||||
|
# positions where the src fiat was used to
|
||||||
|
# buy some other dst which was furhter used
|
||||||
|
# to buy another dst..)
|
||||||
|
log.warning(
|
||||||
|
f'No src fiat {src} found in {bsuid}?'
|
||||||
|
)
|
||||||
|
return
|
||||||
|
|
||||||
|
likely_dst = bsuid[:src_name_start]
|
||||||
|
if likely_dst == dst:
|
||||||
|
return bsuid
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
import sys
|
||||||
|
from pprint import pformat
|
||||||
|
|
||||||
|
args = sys.argv
|
||||||
|
assert len(args) > 1, 'Specifiy account(s) from `brokers.toml`'
|
||||||
|
args = args[1:]
|
||||||
|
for acctid in args:
|
||||||
|
broker, name = acctid.split('.')
|
||||||
|
trans, updated_pps = load_pps_from_ledger(broker, name)
|
||||||
|
print(
|
||||||
|
f'Processing transactions into pps for {broker}:{acctid}\n'
|
||||||
|
f'{pformat(trans)}\n\n'
|
||||||
|
f'{pformat(updated_pps)}'
|
||||||
|
)
|
|
@ -0,0 +1,125 @@
|
||||||
|
# piker: trading gear for hackers
|
||||||
|
# Copyright (C) Tyler Goodlet (in stewardship for pikers)
|
||||||
|
|
||||||
|
# This program is free software: you can redistribute it and/or modify
|
||||||
|
# it under the terms of the GNU Affero General Public License as published by
|
||||||
|
# the Free Software Foundation, either version 3 of the License, or
|
||||||
|
# (at your option) any later version.
|
||||||
|
|
||||||
|
# This program is distributed in the hope that it will be useful,
|
||||||
|
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
# GNU Affero General Public License for more details.
|
||||||
|
|
||||||
|
# You should have received a copy of the GNU Affero General Public License
|
||||||
|
|
||||||
|
# along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
from __future__ import annotations
|
||||||
|
from contextlib import contextmanager as cm
|
||||||
|
import os
|
||||||
|
from os import path
|
||||||
|
import time
|
||||||
|
from typing import (
|
||||||
|
Any,
|
||||||
|
Iterator,
|
||||||
|
Union,
|
||||||
|
Generator
|
||||||
|
)
|
||||||
|
|
||||||
|
from pendulum import (
|
||||||
|
datetime,
|
||||||
|
)
|
||||||
|
import tomli
|
||||||
|
import toml
|
||||||
|
|
||||||
|
from .. import config
|
||||||
|
from ..data._source import Symbol
|
||||||
|
from ..data.types import Struct
|
||||||
|
from ..log import get_logger
|
||||||
|
|
||||||
|
log = get_logger(__name__)
|
||||||
|
|
||||||
|
|
||||||
|
@cm
|
||||||
|
def open_trade_ledger(
|
||||||
|
broker: str,
|
||||||
|
account: str,
|
||||||
|
|
||||||
|
) -> Generator[dict, None, None]:
|
||||||
|
'''
|
||||||
|
Indempotently create and read in a trade log file from the
|
||||||
|
``<configuration_dir>/ledgers/`` directory.
|
||||||
|
|
||||||
|
Files are named per broker account of the form
|
||||||
|
``<brokername>_<accountname>.toml``. The ``accountname`` here is the
|
||||||
|
name as defined in the user's ``brokers.toml`` config.
|
||||||
|
|
||||||
|
'''
|
||||||
|
ldir = path.join(config._config_dir, 'ledgers')
|
||||||
|
if not path.isdir(ldir):
|
||||||
|
os.makedirs(ldir)
|
||||||
|
|
||||||
|
fname = f'trades_{broker}_{account}.toml'
|
||||||
|
tradesfile = path.join(ldir, fname)
|
||||||
|
|
||||||
|
if not path.isfile(tradesfile):
|
||||||
|
log.info(
|
||||||
|
f'Creating new local trades ledger: {tradesfile}'
|
||||||
|
)
|
||||||
|
with open(tradesfile, 'w') as cf:
|
||||||
|
pass # touch
|
||||||
|
with open(tradesfile, 'rb') as cf:
|
||||||
|
start = time.time()
|
||||||
|
ledger = tomli.load(cf)
|
||||||
|
log.info(f'Ledger load took {time.time() - start}s')
|
||||||
|
cpy = ledger.copy()
|
||||||
|
|
||||||
|
try:
|
||||||
|
yield cpy
|
||||||
|
finally:
|
||||||
|
if cpy != ledger:
|
||||||
|
|
||||||
|
# TODO: show diff output?
|
||||||
|
# https://stackoverflow.com/questions/12956957/print-diff-of-python-dictionaries
|
||||||
|
log.info(f'Updating ledger for {tradesfile}:\n')
|
||||||
|
ledger.update(cpy)
|
||||||
|
|
||||||
|
# we write on close the mutated ledger data
|
||||||
|
with open(tradesfile, 'w') as cf:
|
||||||
|
toml.dump(ledger, cf)
|
||||||
|
|
||||||
|
|
||||||
|
class Transaction(Struct, frozen=True):
|
||||||
|
# TODO: should this be ``.to`` (see below)?
|
||||||
|
fqsn: str
|
||||||
|
|
||||||
|
sym: Symbol
|
||||||
|
tid: Union[str, int] # unique transaction id
|
||||||
|
size: float
|
||||||
|
price: float
|
||||||
|
cost: float # commisions or other additional costs
|
||||||
|
dt: datetime
|
||||||
|
expiry: datetime | None = None
|
||||||
|
|
||||||
|
# optional key normally derived from the broker
|
||||||
|
# backend which ensures the instrument-symbol this record
|
||||||
|
# is for is truly unique.
|
||||||
|
bsuid: Union[str, int] | None = None
|
||||||
|
|
||||||
|
# optional fqsn for the source "asset"/money symbol?
|
||||||
|
# from: Optional[str] = None
|
||||||
|
|
||||||
|
|
||||||
|
def iter_by_dt(
|
||||||
|
clears: dict[str, Any],
|
||||||
|
) -> Iterator[tuple[str, dict]]:
|
||||||
|
'''
|
||||||
|
Iterate entries of a ``clears: dict`` table sorted by entry recorded
|
||||||
|
datetime presumably set at the ``'dt'`` field in each entry.
|
||||||
|
|
||||||
|
'''
|
||||||
|
for tid, data in sorted(
|
||||||
|
list(clears.items()),
|
||||||
|
key=lambda item: item[1]['dt'],
|
||||||
|
):
|
||||||
|
yield tid, data
|
|
@ -14,20 +14,18 @@
|
||||||
# You should have received a copy of the GNU Affero General Public License
|
# You should have received a copy of the GNU Affero General Public License
|
||||||
|
|
||||||
# along with this program. If not, see <https://www.gnu.org/licenses/>.
|
# along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
'''
|
'''
|
||||||
Personal/Private position parsing, calculating, summarizing in a way
|
Personal/Private position parsing, calculating, summarizing in a way
|
||||||
that doesn't try to cuk most humans who prefer to not lose their moneys..
|
that doesn't try to cuk most humans who prefer to not lose their moneys..
|
||||||
|
|
||||||
(looking at you `ib` and dirt-bird friends)
|
(looking at you `ib` and dirt-bird friends)
|
||||||
|
|
||||||
'''
|
'''
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
from contextlib import contextmanager as cm
|
from contextlib import contextmanager as cm
|
||||||
from pprint import pformat
|
|
||||||
import os
|
|
||||||
from os import path
|
|
||||||
from math import copysign
|
from math import copysign
|
||||||
import re
|
import re
|
||||||
import time
|
|
||||||
from typing import (
|
from typing import (
|
||||||
Any,
|
Any,
|
||||||
Iterator,
|
Iterator,
|
||||||
|
@ -38,104 +36,23 @@ from typing import (
|
||||||
|
|
||||||
import pendulum
|
import pendulum
|
||||||
from pendulum import datetime, now
|
from pendulum import datetime, now
|
||||||
import tomli
|
|
||||||
import toml
|
import toml
|
||||||
|
|
||||||
from . import config
|
from ._ledger import (
|
||||||
from .brokers import get_brokermod
|
Transaction,
|
||||||
from .clearing._messages import BrokerdPosition, Status
|
iter_by_dt,
|
||||||
from .data._source import Symbol, unpack_fqsn
|
open_trade_ledger,
|
||||||
from .log import get_logger
|
)
|
||||||
from .data.types import Struct
|
from .. import config
|
||||||
|
from ..brokers import get_brokermod
|
||||||
|
from ..clearing._messages import BrokerdPosition, Status
|
||||||
|
from ..data._source import Symbol, unpack_fqsn
|
||||||
|
from ..data.types import Struct
|
||||||
|
from ..log import get_logger
|
||||||
|
|
||||||
log = get_logger(__name__)
|
log = get_logger(__name__)
|
||||||
|
|
||||||
|
|
||||||
@cm
|
|
||||||
def open_trade_ledger(
|
|
||||||
broker: str,
|
|
||||||
account: str,
|
|
||||||
|
|
||||||
) -> Generator[dict, None, None]:
|
|
||||||
'''
|
|
||||||
Indempotently create and read in a trade log file from the
|
|
||||||
``<configuration_dir>/ledgers/`` directory.
|
|
||||||
|
|
||||||
Files are named per broker account of the form
|
|
||||||
``<brokername>_<accountname>.toml``. The ``accountname`` here is the
|
|
||||||
name as defined in the user's ``brokers.toml`` config.
|
|
||||||
|
|
||||||
'''
|
|
||||||
ldir = path.join(config._config_dir, 'ledgers')
|
|
||||||
if not path.isdir(ldir):
|
|
||||||
os.makedirs(ldir)
|
|
||||||
|
|
||||||
fname = f'trades_{broker}_{account}.toml'
|
|
||||||
tradesfile = path.join(ldir, fname)
|
|
||||||
|
|
||||||
if not path.isfile(tradesfile):
|
|
||||||
log.info(
|
|
||||||
f'Creating new local trades ledger: {tradesfile}'
|
|
||||||
)
|
|
||||||
with open(tradesfile, 'w') as cf:
|
|
||||||
pass # touch
|
|
||||||
with open(tradesfile, 'rb') as cf:
|
|
||||||
start = time.time()
|
|
||||||
ledger = tomli.load(cf)
|
|
||||||
log.info(f'Ledger load took {time.time() - start}s')
|
|
||||||
cpy = ledger.copy()
|
|
||||||
|
|
||||||
try:
|
|
||||||
yield cpy
|
|
||||||
finally:
|
|
||||||
if cpy != ledger:
|
|
||||||
|
|
||||||
# TODO: show diff output?
|
|
||||||
# https://stackoverflow.com/questions/12956957/print-diff-of-python-dictionaries
|
|
||||||
log.info(f'Updating ledger for {tradesfile}:\n')
|
|
||||||
ledger.update(cpy)
|
|
||||||
|
|
||||||
# we write on close the mutated ledger data
|
|
||||||
with open(tradesfile, 'w') as cf:
|
|
||||||
toml.dump(ledger, cf)
|
|
||||||
|
|
||||||
|
|
||||||
class Transaction(Struct, frozen=True):
|
|
||||||
# TODO: should this be ``.to`` (see below)?
|
|
||||||
fqsn: str
|
|
||||||
|
|
||||||
sym: Symbol
|
|
||||||
tid: Union[str, int] # unique transaction id
|
|
||||||
size: float
|
|
||||||
price: float
|
|
||||||
cost: float # commisions or other additional costs
|
|
||||||
dt: datetime
|
|
||||||
expiry: datetime | None = None
|
|
||||||
|
|
||||||
# optional key normally derived from the broker
|
|
||||||
# backend which ensures the instrument-symbol this record
|
|
||||||
# is for is truly unique.
|
|
||||||
bsuid: Union[str, int] | None = None
|
|
||||||
|
|
||||||
# optional fqsn for the source "asset"/money symbol?
|
|
||||||
# from: Optional[str] = None
|
|
||||||
|
|
||||||
|
|
||||||
def iter_by_dt(
|
|
||||||
clears: dict[str, Any],
|
|
||||||
) -> Iterator[tuple[str, dict]]:
|
|
||||||
'''
|
|
||||||
Iterate entries of a ``clears: dict`` table sorted by entry recorded
|
|
||||||
datetime presumably set at the ``'dt'`` field in each entry.
|
|
||||||
|
|
||||||
'''
|
|
||||||
for tid, data in sorted(
|
|
||||||
list(clears.items()),
|
|
||||||
key=lambda item: item[1]['dt'],
|
|
||||||
):
|
|
||||||
yield tid, data
|
|
||||||
|
|
||||||
|
|
||||||
class Position(Struct):
|
class Position(Struct):
|
||||||
'''
|
'''
|
||||||
Basic pp (personal/piker position) model with attached clearing
|
Basic pp (personal/piker position) model with attached clearing
|
||||||
|
@ -484,7 +401,9 @@ class Position(Struct):
|
||||||
if self.split_ratio is not None:
|
if self.split_ratio is not None:
|
||||||
size = round(size * self.split_ratio)
|
size = round(size * self.split_ratio)
|
||||||
|
|
||||||
return float(self.symbol.quantize_size(size))
|
return float(
|
||||||
|
self.symbol.quantize_size(size),
|
||||||
|
)
|
||||||
|
|
||||||
def minimize_clears(
|
def minimize_clears(
|
||||||
self,
|
self,
|
||||||
|
@ -564,9 +483,13 @@ class PpTable(Struct):
|
||||||
pps = self.pps
|
pps = self.pps
|
||||||
updated: dict[str, Position] = {}
|
updated: dict[str, Position] = {}
|
||||||
|
|
||||||
# lifo update all pps from records
|
# lifo update all pps from records, ensuring
|
||||||
for tid, t in trans.items():
|
# we compute the PPU and size sorted in time!
|
||||||
|
for t in sorted(
|
||||||
|
trans.values(),
|
||||||
|
key=lambda t: t.dt,
|
||||||
|
reverse=True,
|
||||||
|
):
|
||||||
pp = pps.setdefault(
|
pp = pps.setdefault(
|
||||||
t.bsuid,
|
t.bsuid,
|
||||||
|
|
||||||
|
@ -590,7 +513,10 @@ class PpTable(Struct):
|
||||||
# included in the current pps state.
|
# included in the current pps state.
|
||||||
if (
|
if (
|
||||||
t.tid in clears
|
t.tid in clears
|
||||||
or first_clear_dt and t.dt < first_clear_dt
|
or (
|
||||||
|
first_clear_dt
|
||||||
|
and t.dt < first_clear_dt
|
||||||
|
)
|
||||||
):
|
):
|
||||||
# NOTE: likely you'll see repeats of the same
|
# NOTE: likely you'll see repeats of the same
|
||||||
# ``Transaction`` passed in here if/when you are restarting
|
# ``Transaction`` passed in here if/when you are restarting
|
||||||
|
@ -607,6 +533,8 @@ class PpTable(Struct):
|
||||||
for bsuid, pp in updated.items():
|
for bsuid, pp in updated.items():
|
||||||
pp.ensure_state()
|
pp.ensure_state()
|
||||||
|
|
||||||
|
# deliver only the position entries that were actually updated
|
||||||
|
# (modified the state) from the input transaction set.
|
||||||
return updated
|
return updated
|
||||||
|
|
||||||
def dump_active(
|
def dump_active(
|
||||||
|
@ -701,8 +629,10 @@ class PpTable(Struct):
|
||||||
# active, closed_pp_objs = table.dump_active()
|
# active, closed_pp_objs = table.dump_active()
|
||||||
pp_entries = self.to_toml()
|
pp_entries = self.to_toml()
|
||||||
if pp_entries:
|
if pp_entries:
|
||||||
log.info(f'Updating ``pps.toml`` for {path}:\n')
|
log.info(
|
||||||
log.info(f'Current positions:\n{pp_entries}')
|
f'Updating ``pps.toml``:\n'
|
||||||
|
f'Current positions:\n{pp_entries}'
|
||||||
|
)
|
||||||
self.conf[self.brokername][self.acctid] = pp_entries
|
self.conf[self.brokername][self.acctid] = pp_entries
|
||||||
|
|
||||||
elif (
|
elif (
|
||||||
|
@ -1029,19 +959,3 @@ def open_pps(
|
||||||
finally:
|
finally:
|
||||||
if write_on_exit:
|
if write_on_exit:
|
||||||
table.write_config()
|
table.write_config()
|
||||||
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
|
||||||
import sys
|
|
||||||
|
|
||||||
args = sys.argv
|
|
||||||
assert len(args) > 1, 'Specifiy account(s) from `brokers.toml`'
|
|
||||||
args = args[1:]
|
|
||||||
for acctid in args:
|
|
||||||
broker, name = acctid.split('.')
|
|
||||||
trans, updated_pps = load_pps_from_ledger(broker, name)
|
|
||||||
print(
|
|
||||||
f'Processing transactions into pps for {broker}:{acctid}\n'
|
|
||||||
f'{pformat(trans)}\n\n'
|
|
||||||
f'{pformat(updated_pps)}'
|
|
||||||
)
|
|
|
@ -24,6 +24,10 @@ import subprocess
|
||||||
|
|
||||||
import tractor
|
import tractor
|
||||||
|
|
||||||
|
from piker.log import get_logger
|
||||||
|
|
||||||
|
log = get_logger(__name__)
|
||||||
|
|
||||||
|
|
||||||
_reset_tech: Literal[
|
_reset_tech: Literal[
|
||||||
'vnc',
|
'vnc',
|
||||||
|
@ -134,54 +138,54 @@ def i3ipc_xdotool_manual_click_hack() -> None:
|
||||||
# 'IB', # gw running in i3 (newer version?)
|
# 'IB', # gw running in i3 (newer version?)
|
||||||
]
|
]
|
||||||
|
|
||||||
for name in win_names:
|
try:
|
||||||
results = t.find_titled(name)
|
for name in win_names:
|
||||||
print(f'results for {name}: {results}')
|
results = t.find_titled(name)
|
||||||
if results:
|
print(f'results for {name}: {results}')
|
||||||
con = results[0]
|
if results:
|
||||||
print(f'Resetting data feed for {name}')
|
con = results[0]
|
||||||
win_id = str(con.window)
|
print(f'Resetting data feed for {name}')
|
||||||
w, h = con.rect.width, con.rect.height
|
win_id = str(con.window)
|
||||||
|
w, h = con.rect.width, con.rect.height
|
||||||
|
|
||||||
# TODO: seems to be a few libs for python but not sure
|
# TODO: seems to be a few libs for python but not sure
|
||||||
# if they support all the sub commands we need, order of
|
# if they support all the sub commands we need, order of
|
||||||
# most recent commit history:
|
# most recent commit history:
|
||||||
# https://github.com/rr-/pyxdotool
|
# https://github.com/rr-/pyxdotool
|
||||||
# https://github.com/ShaneHutter/pyxdotool
|
# https://github.com/ShaneHutter/pyxdotool
|
||||||
# https://github.com/cphyc/pyxdotool
|
# https://github.com/cphyc/pyxdotool
|
||||||
|
|
||||||
# TODO: only run the reconnect (2nd) kc on a detected
|
# TODO: only run the reconnect (2nd) kc on a detected
|
||||||
# disconnect?
|
# disconnect?
|
||||||
for key_combo, timeout in [
|
for key_combo, timeout in [
|
||||||
# only required if we need a connection reset.
|
# only required if we need a connection reset.
|
||||||
# ('ctrl+alt+r', 12),
|
# ('ctrl+alt+r', 12),
|
||||||
# data feed reset.
|
# data feed reset.
|
||||||
('ctrl+alt+f', 6)
|
('ctrl+alt+f', 6)
|
||||||
]:
|
]:
|
||||||
subprocess.call([
|
subprocess.call([
|
||||||
'xdotool',
|
'xdotool',
|
||||||
'windowactivate', '--sync', win_id,
|
'windowactivate', '--sync', win_id,
|
||||||
|
|
||||||
# move mouse to bottom left of window (where there should
|
# move mouse to bottom left of window (where
|
||||||
# be nothing to click).
|
# there should be nothing to click).
|
||||||
'mousemove_relative', '--sync', str(w-4), str(h-4),
|
'mousemove_relative', '--sync', str(w-4), str(h-4),
|
||||||
|
|
||||||
# NOTE: we may need to stick a `--retry 3` in here..
|
# NOTE: we may need to stick a `--retry 3` in here..
|
||||||
'click', '--window', win_id,
|
'click', '--window', win_id,
|
||||||
'--repeat', '3', '1',
|
'--repeat', '3', '1',
|
||||||
|
|
||||||
# hackzorzes
|
# hackzorzes
|
||||||
'key', key_combo,
|
'key', key_combo,
|
||||||
],
|
],
|
||||||
timeout=timeout,
|
timeout=timeout,
|
||||||
)
|
)
|
||||||
|
|
||||||
# re-activate and focus original window
|
# re-activate and focus original window
|
||||||
try:
|
|
||||||
subprocess.call([
|
subprocess.call([
|
||||||
'xdotool',
|
'xdotool',
|
||||||
'windowactivate', '--sync', str(orig_win_id),
|
'windowactivate', '--sync', str(orig_win_id),
|
||||||
'click', '--window', str(orig_win_id), '1',
|
'click', '--window', str(orig_win_id), '1',
|
||||||
])
|
])
|
||||||
except subprocess.TimeoutExpired:
|
except subprocess.TimeoutExpired:
|
||||||
log.exception(f'xdotool timed out?')
|
log.exception('xdotool timed out?')
|
||||||
|
|
|
@ -51,7 +51,7 @@ from ib_insync.objects import Position as IbPosition
|
||||||
import pendulum
|
import pendulum
|
||||||
|
|
||||||
from piker import config
|
from piker import config
|
||||||
from piker.pp import (
|
from piker.accounting import (
|
||||||
Position,
|
Position,
|
||||||
Transaction,
|
Transaction,
|
||||||
open_trade_ledger,
|
open_trade_ledger,
|
||||||
|
@ -1153,7 +1153,7 @@ def norm_trade_records(
|
||||||
|
|
||||||
# special handling of symbol extraction from
|
# special handling of symbol extraction from
|
||||||
# flex records using some ad-hoc schema parsing.
|
# flex records using some ad-hoc schema parsing.
|
||||||
asset_type: str = record.get('assetCategory') or record['secType']
|
asset_type: str = record.get('assetCategory') or record.get('secType', 'STK')
|
||||||
|
|
||||||
# TODO: XXX: WOA this is kinda hacky.. probably
|
# TODO: XXX: WOA this is kinda hacky.. probably
|
||||||
# should figure out the correct future pair key more
|
# should figure out the correct future pair key more
|
||||||
|
|
|
@ -20,6 +20,7 @@ Kraken web API wrapping.
|
||||||
'''
|
'''
|
||||||
from contextlib import asynccontextmanager as acm
|
from contextlib import asynccontextmanager as acm
|
||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
|
from decimal import Decimal
|
||||||
import itertools
|
import itertools
|
||||||
from typing import (
|
from typing import (
|
||||||
Any,
|
Any,
|
||||||
|
@ -48,7 +49,7 @@ from piker.brokers._util import (
|
||||||
BrokerError,
|
BrokerError,
|
||||||
DataThrottle,
|
DataThrottle,
|
||||||
)
|
)
|
||||||
from piker.pp import Transaction
|
from piker.accounting import Transaction
|
||||||
from . import log
|
from . import log
|
||||||
|
|
||||||
# <uri>/<version>/
|
# <uri>/<version>/
|
||||||
|
@ -248,6 +249,9 @@ class Client:
|
||||||
{},
|
{},
|
||||||
)
|
)
|
||||||
by_bsuid = resp['result']
|
by_bsuid = resp['result']
|
||||||
|
|
||||||
|
# TODO: we need to pull out the "asset" decimals
|
||||||
|
# data and return a `decimal.Decimal` instead here!
|
||||||
return {
|
return {
|
||||||
self._atable[sym].lower(): float(bal)
|
self._atable[sym].lower(): float(bal)
|
||||||
for sym, bal in by_bsuid.items()
|
for sym, bal in by_bsuid.items()
|
||||||
|
|
|
@ -21,7 +21,6 @@ Order api and machinery
|
||||||
from collections import ChainMap, defaultdict
|
from collections import ChainMap, defaultdict
|
||||||
from contextlib import (
|
from contextlib import (
|
||||||
asynccontextmanager as acm,
|
asynccontextmanager as acm,
|
||||||
contextmanager as cm,
|
|
||||||
)
|
)
|
||||||
from functools import partial
|
from functools import partial
|
||||||
from itertools import count
|
from itertools import count
|
||||||
|
@ -41,14 +40,18 @@ import pendulum
|
||||||
import trio
|
import trio
|
||||||
import tractor
|
import tractor
|
||||||
|
|
||||||
from piker.pp import (
|
from piker.accounting import (
|
||||||
Position,
|
Position,
|
||||||
PpTable,
|
PpTable,
|
||||||
Transaction,
|
Transaction,
|
||||||
open_trade_ledger,
|
open_trade_ledger,
|
||||||
open_pps,
|
open_pps,
|
||||||
|
get_likely_pair,
|
||||||
|
)
|
||||||
|
from piker.data._source import (
|
||||||
|
Symbol,
|
||||||
|
digits_to_dec,
|
||||||
)
|
)
|
||||||
from piker.data._source import Symbol
|
|
||||||
from piker.clearing._messages import (
|
from piker.clearing._messages import (
|
||||||
Order,
|
Order,
|
||||||
Status,
|
Status,
|
||||||
|
@ -470,12 +473,14 @@ async def trades_dialogue(
|
||||||
with (
|
with (
|
||||||
open_pps(
|
open_pps(
|
||||||
'kraken',
|
'kraken',
|
||||||
acctid
|
acctid,
|
||||||
|
write_on_exit=True,
|
||||||
|
|
||||||
) as table,
|
) as table,
|
||||||
|
|
||||||
open_trade_ledger(
|
open_trade_ledger(
|
||||||
'kraken',
|
'kraken',
|
||||||
acctid
|
acctid,
|
||||||
) as ledger_dict,
|
) as ledger_dict,
|
||||||
):
|
):
|
||||||
# transaction-ify the ledger entries
|
# transaction-ify the ledger entries
|
||||||
|
@ -494,7 +499,10 @@ async def trades_dialogue(
|
||||||
# what amount of trades-transactions need
|
# what amount of trades-transactions need
|
||||||
# to be reloaded.
|
# to be reloaded.
|
||||||
balances = await client.get_balances()
|
balances = await client.get_balances()
|
||||||
|
# await tractor.breakpoint()
|
||||||
|
|
||||||
for dst, size in balances.items():
|
for dst, size in balances.items():
|
||||||
|
|
||||||
# we don't care about tracking positions
|
# we don't care about tracking positions
|
||||||
# in the user's source fiat currency.
|
# in the user's source fiat currency.
|
||||||
if (
|
if (
|
||||||
|
@ -508,45 +516,20 @@ async def trades_dialogue(
|
||||||
)
|
)
|
||||||
continue
|
continue
|
||||||
|
|
||||||
def get_likely_pair(
|
|
||||||
dst: str,
|
|
||||||
bsuid: str,
|
|
||||||
src_fiat: str = src_fiat
|
|
||||||
|
|
||||||
) -> str:
|
|
||||||
'''
|
|
||||||
Attempt to get the likely trading pair masting
|
|
||||||
a given destination asset `dst: str`.
|
|
||||||
|
|
||||||
'''
|
|
||||||
try:
|
|
||||||
src_name_start = bsuid.rindex(src_fiat)
|
|
||||||
except (
|
|
||||||
ValueError, # substr not found
|
|
||||||
):
|
|
||||||
# TODO: handle nested positions..(i.e.
|
|
||||||
# positions where the src fiat was used to
|
|
||||||
# buy some other dst which was furhter used
|
|
||||||
# to buy another dst..)
|
|
||||||
log.warning(
|
|
||||||
f'No src fiat {src_fiat} found in {bsuid}?'
|
|
||||||
)
|
|
||||||
return
|
|
||||||
|
|
||||||
likely_dst = bsuid[:src_name_start]
|
|
||||||
if likely_dst == dst:
|
|
||||||
return bsuid
|
|
||||||
|
|
||||||
def has_pp(
|
def has_pp(
|
||||||
dst: str,
|
dst: str,
|
||||||
size: float,
|
size: float,
|
||||||
|
|
||||||
) -> Position | bool:
|
) -> Position | None:
|
||||||
|
|
||||||
src2dst: dict[str, str] = {}
|
src2dst: dict[str, str] = {}
|
||||||
|
|
||||||
for bsuid in table.pps:
|
for bsuid in table.pps:
|
||||||
likely_pair = get_likely_pair(dst, bsuid)
|
likely_pair = get_likely_pair(
|
||||||
|
src_fiat,
|
||||||
|
dst,
|
||||||
|
bsuid,
|
||||||
|
)
|
||||||
if likely_pair:
|
if likely_pair:
|
||||||
src2dst[src_fiat] = dst
|
src2dst[src_fiat] = dst
|
||||||
|
|
||||||
|
@ -574,7 +557,7 @@ async def trades_dialogue(
|
||||||
)
|
)
|
||||||
return pp
|
return pp
|
||||||
|
|
||||||
return False
|
return None # signal no entry
|
||||||
|
|
||||||
pos = has_pp(dst, size)
|
pos = has_pp(dst, size)
|
||||||
if not pos:
|
if not pos:
|
||||||
|
@ -602,7 +585,11 @@ async def trades_dialogue(
|
||||||
# yet and thus this likely pair grabber will
|
# yet and thus this likely pair grabber will
|
||||||
# likely fail.
|
# likely fail.
|
||||||
for bsuid in table.pps:
|
for bsuid in table.pps:
|
||||||
likely_pair = get_likely_pair(dst, bsuid)
|
likely_pair = get_likely_pair(
|
||||||
|
src_fiat,
|
||||||
|
dst,
|
||||||
|
bsuid,
|
||||||
|
)
|
||||||
if likely_pair:
|
if likely_pair:
|
||||||
break
|
break
|
||||||
else:
|
else:
|
||||||
|
@ -724,8 +711,8 @@ async def handle_order_updates(
|
||||||
'''
|
'''
|
||||||
Main msg handling loop for all things order management.
|
Main msg handling loop for all things order management.
|
||||||
|
|
||||||
This code is broken out to make the context explicit and state variables
|
This code is broken out to make the context explicit and state
|
||||||
defined in the signature clear to the reader.
|
variables defined in the signature clear to the reader.
|
||||||
|
|
||||||
'''
|
'''
|
||||||
async for msg in ws_stream:
|
async for msg in ws_stream:
|
||||||
|
@ -1204,7 +1191,13 @@ def norm_trade_records(
|
||||||
fqsn,
|
fqsn,
|
||||||
info={
|
info={
|
||||||
'lot_size_digits': pair_info.lot_decimals,
|
'lot_size_digits': pair_info.lot_decimals,
|
||||||
|
'lot_tick_size': digits_to_dec(
|
||||||
|
pair_info.lot_decimals,
|
||||||
|
),
|
||||||
'tick_size_digits': pair_info.pair_decimals,
|
'tick_size_digits': pair_info.pair_decimals,
|
||||||
|
'price_tick_size': digits_to_dec(
|
||||||
|
pair_info.pair_decimals,
|
||||||
|
),
|
||||||
'asset_type': 'crypto',
|
'asset_type': 'crypto',
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
|
|
|
@ -25,7 +25,7 @@ from bidict import bidict
|
||||||
|
|
||||||
from ..data._source import Symbol
|
from ..data._source import Symbol
|
||||||
from ..data.types import Struct
|
from ..data.types import Struct
|
||||||
from ..pp import Position
|
from ..accounting import Position
|
||||||
|
|
||||||
|
|
||||||
_size_units = bidict({
|
_size_units = bidict({
|
||||||
|
|
|
@ -39,7 +39,7 @@ import tractor
|
||||||
from .. import data
|
from .. import data
|
||||||
from ..data.types import Struct
|
from ..data.types import Struct
|
||||||
from ..data._source import Symbol
|
from ..data._source import Symbol
|
||||||
from ..pp import (
|
from ..accounting import (
|
||||||
Position,
|
Position,
|
||||||
Transaction,
|
Transaction,
|
||||||
open_trade_ledger,
|
open_trade_ledger,
|
||||||
|
@ -58,8 +58,6 @@ from ._messages import (
|
||||||
BrokerdError,
|
BrokerdError,
|
||||||
)
|
)
|
||||||
|
|
||||||
from ..config import load
|
|
||||||
|
|
||||||
log = get_logger(__name__)
|
log = get_logger(__name__)
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -90,6 +90,21 @@ def float_digits(
|
||||||
return int(-Decimal(str(value)).as_tuple().exponent)
|
return int(-Decimal(str(value)).as_tuple().exponent)
|
||||||
|
|
||||||
|
|
||||||
|
def digits_to_dec(
|
||||||
|
ndigits: int,
|
||||||
|
) -> Decimal:
|
||||||
|
'''
|
||||||
|
Return the minimum float value for an input integer value.
|
||||||
|
|
||||||
|
eg. 3 -> 0.001
|
||||||
|
|
||||||
|
'''
|
||||||
|
if ndigits == 0:
|
||||||
|
return Decimal('0')
|
||||||
|
|
||||||
|
return Decimal('0.' + '0'*(ndigits-1) + '1')
|
||||||
|
|
||||||
|
|
||||||
def ohlc_zeros(length: int) -> np.ndarray:
|
def ohlc_zeros(length: int) -> np.ndarray:
|
||||||
"""Construct an OHLC field formatted structarray.
|
"""Construct an OHLC field formatted structarray.
|
||||||
|
|
||||||
|
@ -213,10 +228,13 @@ class Symbol(Struct):
|
||||||
|
|
||||||
return Symbol(
|
return Symbol(
|
||||||
key=symbol,
|
key=symbol,
|
||||||
|
|
||||||
tick_size=tick_size,
|
tick_size=tick_size,
|
||||||
lot_tick_size=lot_size,
|
lot_tick_size=lot_size,
|
||||||
|
|
||||||
tick_size_digits=float_digits(tick_size),
|
tick_size_digits=float_digits(tick_size),
|
||||||
lot_size_digits=float_digits(lot_size),
|
lot_size_digits=float_digits(lot_size),
|
||||||
|
|
||||||
suffix=suffix,
|
suffix=suffix,
|
||||||
broker_info={broker: info},
|
broker_info={broker: info},
|
||||||
)
|
)
|
||||||
|
|
|
@ -47,7 +47,7 @@ from ..calc import (
|
||||||
puterize,
|
puterize,
|
||||||
)
|
)
|
||||||
from ..clearing._allocate import Allocator
|
from ..clearing._allocate import Allocator
|
||||||
from ..pp import Position
|
from ..accounting import Position
|
||||||
from ..data._normalize import iterticks
|
from ..data._normalize import iterticks
|
||||||
from ..data.feed import (
|
from ..data.feed import (
|
||||||
Feed,
|
Feed,
|
||||||
|
|
|
@ -37,7 +37,7 @@ import trio
|
||||||
from PyQt5.QtCore import Qt
|
from PyQt5.QtCore import Qt
|
||||||
|
|
||||||
from .. import config
|
from .. import config
|
||||||
from ..pp import Position
|
from ..accounting import Position
|
||||||
from ..clearing._client import open_ems, OrderBook
|
from ..clearing._client import open_ems, OrderBook
|
||||||
from ..clearing._allocate import (
|
from ..clearing._allocate import (
|
||||||
mk_allocator,
|
mk_allocator,
|
||||||
|
|
|
@ -16,7 +16,7 @@ from functools import partial
|
||||||
|
|
||||||
from piker.log import get_logger
|
from piker.log import get_logger
|
||||||
from piker.clearing._messages import Order
|
from piker.clearing._messages import Order
|
||||||
from piker.pp import (
|
from piker.accounting import (
|
||||||
open_pps,
|
open_pps,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue