Handle read and write of `pps.toml` using `MktPair`

Add a logic branch for now that switches on an instance check.
Generally swap over all `Position.symbol` and `Transaction.sym` refs to
`MktPair`. Do a wholesale rename of all `.bsuid` var names to
`.bs_mktid`.
rekt_pps
Tyler Goodlet 2023-03-17 19:45:43 -04:00
parent 7b28c7a43f
commit 72c97d4672
8 changed files with 112 additions and 89 deletions

View File

@ -48,7 +48,7 @@ __all__ = [
def get_likely_pair( def get_likely_pair(
src: str, src: str,
dst: str, dst: str,
bsuid: str, bs_mktid: str,
) -> str: ) -> str:
''' '''
@ -57,7 +57,7 @@ def get_likely_pair(
''' '''
try: try:
src_name_start = bsuid.rindex(src) src_name_start = bs_mktid.rindex(src)
except ( except (
ValueError, # substr not found ValueError, # substr not found
): ):
@ -66,13 +66,13 @@ def get_likely_pair(
# buy some other dst which was furhter used # buy some other dst which was furhter used
# to buy another dst..) # to buy another dst..)
log.warning( log.warning(
f'No src fiat {src} found in {bsuid}?' f'No src fiat {src} found in {bs_mktid}?'
) )
return return
likely_dst = bsuid[:src_name_start] likely_dst = bs_mktid[:src_name_start]
if likely_dst == dst: if likely_dst == dst:
return bsuid return bs_mktid
if __name__ == '__main__': if __name__ == '__main__':

View File

@ -129,12 +129,7 @@ class Transaction(Struct, frozen=True):
# in the "their backend/system" sense; i.e. this uid for the market # in the "their backend/system" sense; i.e. this uid for the market
# as defined (internally) in some namespace defined by the broker # as defined (internally) in some namespace defined by the broker
# service. # service.
bsuid: str | int | None = None bs_mktid: str | int | None = None
@property
def bs_mktid(self) -> str | int | None:
print(f'STOP USING .bsuid` for {self.fqme}')
return self.bs_mktid
# XXX NOTE: this will come from the `MktPair` # XXX NOTE: this will come from the `MktPair`
# instead of defined here right? # instead of defined here right?

View File

@ -45,7 +45,8 @@ from ._ledger import (
) )
from ._mktinfo import ( from ._mktinfo import (
Symbol, Symbol,
# MktPair, MktPair,
Asset,
unpack_fqsn, unpack_fqsn,
) )
from .. import config from .. import config
@ -63,7 +64,7 @@ class Position(Struct):
transaction history. transaction history.
''' '''
symbol: Symbol # | MktPair symbol: Symbol | MktPair
# can be +ve or -ve for long/short # can be +ve or -ve for long/short
size: float size: float
@ -72,17 +73,17 @@ class Position(Struct):
# zero for the entirety of the current "trade state". # zero for the entirety of the current "trade state".
ppu: float ppu: float
# unique backend symbol id # unique "backend system market id"
bsuid: str bs_mktid: str
split_ratio: Optional[int] = None split_ratio: int | None = None
# ordered record of known constituent trade messages # ordered record of known constituent trade messages
clears: dict[ clears: dict[
Union[str, int, Status], # trade id Union[str, int, Status], # trade id
dict[str, Any], # transaction history summaries dict[str, Any], # transaction history summaries
] = {} ] = {}
first_clear_dt: Optional[datetime] = None first_clear_dt: datetime | None = None
expiry: Optional[datetime] = None expiry: Optional[datetime] = None
@ -117,19 +118,34 @@ class Position(Struct):
fqsn = s.fqme fqsn = s.fqme
broker, key, suffix = unpack_fqsn(fqsn) broker, key, suffix = unpack_fqsn(fqsn)
sym_info = s.broker_info[broker]
d['asset_type'] = sym_info['asset_type'] if isinstance(s, Symbol):
d['price_tick_size'] = ( sym_info = s.broker_info[broker]
sym_info.get('price_tick_size') d['asset_type'] = sym_info['asset_type']
or d['price_tick'] = (
s.tick_size sym_info.get('price_tick_size')
) or
d['lot_tick_size'] = ( s.tick_size
sym_info.get('lot_tick_size') )
or d['size_tick'] = (
s.lot_tick_size sym_info.get('lot_tick_size')
) or
s.lot_tick_size
)
# the newwww wayyy B)
else:
mkt = s
assert isinstance(mkt, MktPair)
# an asset resolved mkt where we have ``Asset`` info about
# each tradeable asset in the market.
if mkt.resolved:
dst: Asset = mkt.dst
d['asset_type'] = dst.atype
d['price_tick'] = mkt.price_tick
d['size_tick'] = mkt.size_tick
if self.expiry is None: if self.expiry is None:
d.pop('expiry', None) d.pop('expiry', None)
@ -217,14 +233,19 @@ class Position(Struct):
# XXX: better place to do this? # XXX: better place to do this?
symbol = self.symbol symbol = self.symbol
lot_size_digits = symbol.lot_size_digits # TODO: switch to new fields..?
# .size_tick_digits, .price_tick_digits
size_tick_digits = symbol.lot_size_digits
price_tick_digits = symbol.tick_size_digits
self.ppu = round( self.ppu = round(
# TODO: change this to ppu?
msg['avg_price'], msg['avg_price'],
ndigits=symbol.tick_size_digits, ndigits=price_tick_digits,
) )
self.size = round( self.size = round(
msg['size'], msg['size'],
ndigits=lot_size_digits, ndigits=size_tick_digits,
) )
@property @property
@ -490,7 +511,7 @@ class PpTable(Struct):
reverse=True, reverse=True,
): ):
pp = pps.setdefault( pp = pps.setdefault(
t.bsuid, t.bs_mktid,
# if no existing pp, allocate fresh one. # if no existing pp, allocate fresh one.
Position( Position(
@ -500,7 +521,7 @@ class PpTable(Struct):
) if not t.sym else t.sym, ) if not t.sym else t.sym,
size=0.0, size=0.0,
ppu=0.0, ppu=0.0,
bsuid=t.bsuid, bs_mktid=t.bs_mktid,
expiry=t.expiry, expiry=t.expiry,
) )
) )
@ -526,10 +547,10 @@ class PpTable(Struct):
# update clearing table # update clearing table
pp.add_clear(t) pp.add_clear(t)
updated[t.bsuid] = pp updated[t.bs_mktid] = pp
# minimize clears tables and update sizing. # minimize clears tables and update sizing.
for bsuid, pp in updated.items(): for bs_mktid, pp in updated.items():
pp.ensure_state() pp.ensure_state()
# deliver only the position entries that were actually updated # deliver only the position entries that were actually updated
@ -557,14 +578,8 @@ class PpTable(Struct):
open_pp_objs: dict[str, Position] = {} open_pp_objs: dict[str, Position] = {}
pp_objs = self.pps pp_objs = self.pps
for bsuid in list(pp_objs): for bs_mktid in list(pp_objs):
pp = pp_objs[bsuid] pp = pp_objs[bs_mktid]
# XXX: debug hook for size mismatches
# qqqbsuid = 320227571
# if bsuid == qqqbsuid:
# breakpoint()
pp.ensure_state() pp.ensure_state()
if ( if (
@ -583,10 +598,10 @@ class PpTable(Struct):
# ignored; the closed positions won't be written to the # ignored; the closed positions won't be written to the
# ``pps.toml`` since ``pp_active_entries`` above is what's # ``pps.toml`` since ``pp_active_entries`` above is what's
# written. # written.
closed_pp_objs[bsuid] = pp closed_pp_objs[bs_mktid] = pp
else: else:
open_pp_objs[bsuid] = pp open_pp_objs[bs_mktid] = pp
return open_pp_objs, closed_pp_objs return open_pp_objs, closed_pp_objs
@ -600,7 +615,7 @@ class PpTable(Struct):
# we don't store in the ``pps.toml``. # we don't store in the ``pps.toml``.
to_toml_dict = {} to_toml_dict = {}
for bsuid, pos in active.items(): for bs_mktid, pos in active.items():
# keep the minimal amount of clears that make up this # keep the minimal amount of clears that make up this
# position since the last net-zero state. # position since the last net-zero state.
@ -674,7 +689,7 @@ def load_pps_from_ledger(
Open a ledger file by broker name and account and read in and Open a ledger file by broker name and account and read in and
process any trade records into our normalized ``Transaction`` form process any trade records into our normalized ``Transaction`` form
and then update the equivalent ``Pptable`` and deliver the two and then update the equivalent ``Pptable`` and deliver the two
bsuid-mapped dict-sets of the transactions and pps. bs_mktid-mapped dict-sets of the transactions and pps.
''' '''
with ( with (
@ -690,9 +705,9 @@ def load_pps_from_ledger(
if filter_by: if filter_by:
records = {} records = {}
bsuids = set(filter_by) bs_mktids = set(filter_by)
for tid, r in src_records.items(): for tid, r in src_records.items():
if r.bsuid in bsuids: if r.bs_mktid in bs_mktids:
records[tid] = r records[tid] = r
else: else:
records = src_records records = src_records
@ -868,22 +883,35 @@ def open_pps(
# unmarshal/load ``pps.toml`` config entries into object form # unmarshal/load ``pps.toml`` config entries into object form
# and update `PpTable` obj entries. # and update `PpTable` obj entries.
for fqsn, entry in pps.items(): for fqme, entry in pps.items():
bsuid = entry['bsuid']
symbol = Symbol.from_fqsn(
fqsn,
# NOTE & TODO: right now we fill in the defaults from # atype = entry.get('asset_type', '<unknown>')
# `.data._source.Symbol` but eventually these should always
# either be already written to the pos table or provided at # unique broker market id
# write time to ensure always having these values somewhere bs_mktid = (
# and thus allowing us to get our pos sizing precision entry.get('bsuid')
# correct! or entry.get('bs_mktid')
info={ )
'asset_type': entry.get('asset_type', '<unknown>'), price_tick = (
'price_tick_size': entry.get('price_tick_size', 0.01), entry.get('price_tick_size')
'lot_tick_size': entry.get('lot_tick_size', 0.0), or entry.get('price_tick')
} or 0.01
)
size_tick = (
entry.get('lot_tick_size')
or entry.get('size_tick')
or 0.0
)
# load the pair using the fqme which
# will make the pair "unresolved" until
# the backend broker actually loads
# the market and position info.
mkt = MktPair.from_fqme(
fqme,
price_tick=price_tick,
size_tick=size_tick,
bs_mktid=bs_mktid
) )
# convert clears sub-tables (only in this form # convert clears sub-tables (only in this form
@ -893,7 +921,7 @@ def open_pps(
# index clears entries in "object" form by tid in a top # index clears entries in "object" form by tid in a top
# level dict instead of a list (as is presented in our # level dict instead of a list (as is presented in our
# ``pps.toml``). # ``pps.toml``).
clears = pp_objs.setdefault(bsuid, {}) clears = pp_objs.setdefault(bs_mktid, {})
# TODO: should be make a ``Struct`` for clear/event entries? # TODO: should be make a ``Struct`` for clear/event entries?
# convert "clear events table" from the toml config (list of # convert "clear events table" from the toml config (list of
@ -908,9 +936,9 @@ def open_pps(
clears_table['dt'] = dt clears_table['dt'] = dt
trans.append(Transaction( trans.append(Transaction(
fqsn=bsuid, fqsn=bs_mktid,
sym=symbol, sym=mkt,
bsuid=bsuid, bs_mktid=bs_mktid,
tid=tid, tid=tid,
size=clears_table['size'], size=clears_table['size'],
price=clears_table['price'], price=clears_table['price'],
@ -933,13 +961,13 @@ def open_pps(
if expiry: if expiry:
expiry = pendulum.parse(expiry) expiry = pendulum.parse(expiry)
pp = pp_objs[bsuid] = Position( pp = pp_objs[bs_mktid] = Position(
symbol, mkt,
size=size, size=size,
ppu=ppu, ppu=ppu,
split_ratio=split_ratio, split_ratio=split_ratio,
expiry=expiry, expiry=expiry,
bsuid=entry['bsuid'], bs_mktid=bs_mktid,
) )
# XXX: super critical, we need to be sure to include # XXX: super critical, we need to be sure to include

View File

@ -127,7 +127,7 @@ your ``pps.toml`` file will have position entries like,
[ib.algopaper."mnq.globex.20221216"] [ib.algopaper."mnq.globex.20221216"]
size = -1.0 size = -1.0
ppu = 12423.630576923071 ppu = 12423.630576923071
bsuid = 515416577 bs_mktid = 515416577
expiry = "2022-12-16T00:00:00+00:00" expiry = "2022-12-16T00:00:00+00:00"
clears = [ clears = [
{ dt = "2022-08-31T18:54:46+00:00", ppu = 12423.630576923071, accum_size = -19.0, price = 12372.75, size = 1.0, cost = 0.57, tid = "0000e1a7.630f5e5a.01.01" }, { dt = "2022-08-31T18:54:46+00:00", ppu = 12423.630576923071, accum_size = -19.0, price = 12372.75, size = 1.0, cost = 0.57, tid = "0000e1a7.630f5e5a.01.01" },

View File

@ -335,12 +335,12 @@ async def update_and_audit_msgs(
msgs: list[BrokerdPosition] = [] msgs: list[BrokerdPosition] = []
for p in pps: for p in pps:
bsuid = p.bsuid bs_mktid = p.bs_mktid
# retreive equivalent ib reported position message # retreive equivalent ib reported position message
# for comparison/audit versus the piker equivalent # for comparison/audit versus the piker equivalent
# breakeven pp calcs. # breakeven pp calcs.
ibppmsg = cids2pps.get((acctid, bsuid)) ibppmsg = cids2pps.get((acctid, bs_mktid))
if ibppmsg: if ibppmsg:
msg = BrokerdPosition( msg = BrokerdPosition(
@ -555,18 +555,18 @@ async def trades_dialogue(
# collect all ib-pp reported positions so that we can be # collect all ib-pp reported positions so that we can be
# sure know which positions to update from the ledger if # sure know which positions to update from the ledger if
# any are missing from the ``pps.toml`` # any are missing from the ``pps.toml``
bsuid, msg = pack_position(pos) bs_mktid, msg = pack_position(pos)
acctid = msg.account = accounts_def.inverse[msg.account] acctid = msg.account = accounts_def.inverse[msg.account]
acctid = acctid.strip('ib.') acctid = acctid.strip('ib.')
cids2pps[(acctid, bsuid)] = msg cids2pps[(acctid, bs_mktid)] = msg
assert msg.account in accounts, ( assert msg.account in accounts, (
f'Position for unknown account: {msg.account}') f'Position for unknown account: {msg.account}')
ledger = ledgers[acctid] ledger = ledgers[acctid]
table = tables[acctid] table = tables[acctid]
pp = table.pps.get(bsuid) pp = table.pps.get(bs_mktid)
if ( if (
not pp not pp
or pp.size != msg.size or pp.size != msg.size
@ -605,12 +605,12 @@ async def trades_dialogue(
# the updated output (maybe this is a bug?) but # the updated output (maybe this is a bug?) but
# if you create a pos from TWS and then load it # if you create a pos from TWS and then load it
# from the api trades it seems we get a key # from the api trades it seems we get a key
# error from ``update[bsuid]`` ? # error from ``update[bs_mktid]`` ?
pp = table.pps.get(bsuid) pp = table.pps.get(bs_mktid)
if not pp: if not pp:
log.error( log.error(
f'The contract id for {msg} may have ' f'The contract id for {msg} may have '
f'changed to {bsuid}\nYou may need to ' f'changed to {bs_mktid}\nYou may need to '
'adjust your ledger for this, skipping ' 'adjust your ledger for this, skipping '
'for now.' 'for now.'
) )
@ -620,8 +620,8 @@ async def trades_dialogue(
# the updated output (maybe this is a bug?) but # the updated output (maybe this is a bug?) but
# if you create a pos from TWS and then load it # if you create a pos from TWS and then load it
# from the api trades it seems we get a key # from the api trades it seems we get a key
# error from ``update[bsuid]`` ? # error from ``update[bs_mktid]`` ?
pp = table.pps[bsuid] pp = table.pps[bs_mktid]
pairinfo = pp.symbol pairinfo = pp.symbol
if msg.size != pp.size: if msg.size != pp.size:
log.error( log.error(
@ -760,7 +760,7 @@ async def emit_pp_update(
# re-formatted pps as msgs to the ems. # re-formatted pps as msgs to the ems.
for pos in filter( for pos in filter(
bool, bool,
[active.get(r.bsuid), closed.get(r.bsuid)] [active.get(r.bs_mktid), closed.get(r.bs_mktid)]
): ):
msgs = await update_and_audit_msgs( msgs = await update_and_audit_msgs(
acctid, acctid,
@ -1225,7 +1225,7 @@ def norm_trade_records(
cost=comms, cost=comms,
dt=dt, dt=dt,
expiry=expiry, expiry=expiry,
bsuid=conid, bs_mktid=conid,
), ),
key=lambda t: t.dt key=lambda t: t.dt
) )

View File

@ -206,7 +206,7 @@ class Allocator(Struct):
symbol=sym, symbol=sym,
size=order_size, size=order_size,
ppu=price, ppu=price,
bsuid=sym, bs_mktid=sym,
) )
) )

View File

@ -1,5 +1,5 @@
# piker: trading gear for hackers # piker: trading gear for hackers
# Copyright (C) Tyler Goodlet (in stewardship for piker0) # Copyright (C) Tyler Goodlet (in stewardship for pikers)
# This program is free software: you can redistribute it and/or modify # 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 # it under the terms of the GNU Affero General Public License as published by
@ -258,7 +258,7 @@ class PaperBoi(Struct):
price=price, price=price,
cost=0, # TODO: cost model cost=0, # TODO: cost model
dt=pendulum.from_timestamp(fill_time_s), dt=pendulum.from_timestamp(fill_time_s),
bsuid=key, bs_mktid=key,
) )
with ( with (

View File

@ -737,7 +737,7 @@ async def open_order_mode(
ppu=0, ppu=0,
# XXX: BLEH, do we care about this on the client side? # XXX: BLEH, do we care about this on the client side?
bsuid=symbol, bs_mktid=symbol.key,
) )
# allocator config # allocator config