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
							parent
							
								
									7b28c7a43f
								
							
						
					
					
						commit
						72c97d4672
					
				|  | @ -48,7 +48,7 @@ __all__ = [ | |||
| def get_likely_pair( | ||||
|     src: str, | ||||
|     dst: str, | ||||
|     bsuid: str, | ||||
|     bs_mktid: str, | ||||
| 
 | ||||
| ) -> str: | ||||
|     ''' | ||||
|  | @ -57,7 +57,7 @@ def get_likely_pair( | |||
| 
 | ||||
|     ''' | ||||
|     try: | ||||
|         src_name_start = bsuid.rindex(src) | ||||
|         src_name_start = bs_mktid.rindex(src) | ||||
|     except ( | ||||
|         ValueError,   # substr not found | ||||
|     ): | ||||
|  | @ -66,13 +66,13 @@ def get_likely_pair( | |||
|         # buy some other dst which was furhter used | ||||
|         # to buy another dst..) | ||||
|         log.warning( | ||||
|             f'No src fiat {src} found in {bsuid}?' | ||||
|             f'No src fiat {src} found in {bs_mktid}?' | ||||
|         ) | ||||
|         return | ||||
| 
 | ||||
|     likely_dst = bsuid[:src_name_start] | ||||
|     likely_dst = bs_mktid[:src_name_start] | ||||
|     if likely_dst == dst: | ||||
|         return bsuid | ||||
|         return bs_mktid | ||||
| 
 | ||||
| 
 | ||||
| if __name__ == '__main__': | ||||
|  |  | |||
|  | @ -129,12 +129,7 @@ class Transaction(Struct, frozen=True): | |||
|     # in the "their backend/system" sense; i.e. this uid for the market | ||||
|     # as defined (internally) in some namespace defined by the broker | ||||
|     # service. | ||||
|     bsuid: str | int | None = None | ||||
| 
 | ||||
|     @property | ||||
|     def bs_mktid(self) -> str | int | None: | ||||
|         print(f'STOP USING .bsuid` for {self.fqme}') | ||||
|         return self.bs_mktid | ||||
|     bs_mktid: str | int | None = None | ||||
| 
 | ||||
|     # XXX NOTE: this will come from the `MktPair` | ||||
|     # instead of defined here right? | ||||
|  |  | |||
|  | @ -45,7 +45,8 @@ from ._ledger import ( | |||
| ) | ||||
| from ._mktinfo import ( | ||||
|     Symbol, | ||||
|     # MktPair, | ||||
|     MktPair, | ||||
|     Asset, | ||||
|     unpack_fqsn, | ||||
| ) | ||||
| from .. import config | ||||
|  | @ -63,7 +64,7 @@ class Position(Struct): | |||
|     transaction history. | ||||
| 
 | ||||
|     ''' | ||||
|     symbol: Symbol  # | MktPair | ||||
|     symbol: Symbol | MktPair | ||||
| 
 | ||||
|     # can be +ve or -ve for long/short | ||||
|     size: float | ||||
|  | @ -72,17 +73,17 @@ class Position(Struct): | |||
|     # zero for the entirety of the current "trade state". | ||||
|     ppu: float | ||||
| 
 | ||||
|     # unique backend symbol id | ||||
|     bsuid: str | ||||
|     # unique "backend system market id" | ||||
|     bs_mktid: str | ||||
| 
 | ||||
|     split_ratio: Optional[int] = None | ||||
|     split_ratio: int | None = None | ||||
| 
 | ||||
|     # ordered record of known constituent trade messages | ||||
|     clears: dict[ | ||||
|         Union[str, int, Status],  # trade id | ||||
|         dict[str, Any],  # transaction history summaries | ||||
|     ] = {} | ||||
|     first_clear_dt: Optional[datetime] = None | ||||
|     first_clear_dt: datetime | None = None | ||||
| 
 | ||||
|     expiry: Optional[datetime] = None | ||||
| 
 | ||||
|  | @ -117,19 +118,34 @@ class Position(Struct): | |||
|         fqsn = s.fqme | ||||
| 
 | ||||
|         broker, key, suffix = unpack_fqsn(fqsn) | ||||
|         sym_info = s.broker_info[broker] | ||||
| 
 | ||||
|         d['asset_type'] = sym_info['asset_type'] | ||||
|         d['price_tick_size'] = ( | ||||
|             sym_info.get('price_tick_size') | ||||
|             or | ||||
|             s.tick_size | ||||
|         ) | ||||
|         d['lot_tick_size'] = ( | ||||
|             sym_info.get('lot_tick_size') | ||||
|             or | ||||
|             s.lot_tick_size | ||||
|         ) | ||||
|         if isinstance(s, Symbol): | ||||
|             sym_info = s.broker_info[broker] | ||||
|             d['asset_type'] = sym_info['asset_type'] | ||||
|             d['price_tick'] = ( | ||||
|                 sym_info.get('price_tick_size') | ||||
|                 or | ||||
|                 s.tick_size | ||||
|             ) | ||||
|             d['size_tick'] = ( | ||||
|                 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: | ||||
|             d.pop('expiry', None) | ||||
|  | @ -217,14 +233,19 @@ class Position(Struct): | |||
|         # XXX: better place to do this? | ||||
|         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( | ||||
|             # TODO: change this to ppu? | ||||
|             msg['avg_price'], | ||||
|             ndigits=symbol.tick_size_digits, | ||||
|             ndigits=price_tick_digits, | ||||
|         ) | ||||
|         self.size = round( | ||||
|             msg['size'], | ||||
|             ndigits=lot_size_digits, | ||||
|             ndigits=size_tick_digits, | ||||
|         ) | ||||
| 
 | ||||
|     @property | ||||
|  | @ -490,7 +511,7 @@ class PpTable(Struct): | |||
|             reverse=True, | ||||
|         ): | ||||
|             pp = pps.setdefault( | ||||
|                 t.bsuid, | ||||
|                 t.bs_mktid, | ||||
| 
 | ||||
|                 # if no existing pp, allocate fresh one. | ||||
|                 Position( | ||||
|  | @ -500,7 +521,7 @@ class PpTable(Struct): | |||
|                     ) if not t.sym else t.sym, | ||||
|                     size=0.0, | ||||
|                     ppu=0.0, | ||||
|                     bsuid=t.bsuid, | ||||
|                     bs_mktid=t.bs_mktid, | ||||
|                     expiry=t.expiry, | ||||
|                 ) | ||||
|             ) | ||||
|  | @ -526,10 +547,10 @@ class PpTable(Struct): | |||
| 
 | ||||
|             # update clearing table | ||||
|             pp.add_clear(t) | ||||
|             updated[t.bsuid] = pp | ||||
|             updated[t.bs_mktid] = pp | ||||
| 
 | ||||
|         # minimize clears tables and update sizing. | ||||
|         for bsuid, pp in updated.items(): | ||||
|         for bs_mktid, pp in updated.items(): | ||||
|             pp.ensure_state() | ||||
| 
 | ||||
|         # deliver only the position entries that were actually updated | ||||
|  | @ -557,14 +578,8 @@ class PpTable(Struct): | |||
|         open_pp_objs: dict[str, Position] = {} | ||||
| 
 | ||||
|         pp_objs = self.pps | ||||
|         for bsuid in list(pp_objs): | ||||
|             pp = pp_objs[bsuid] | ||||
| 
 | ||||
|             # XXX: debug hook for size mismatches | ||||
|             # qqqbsuid = 320227571 | ||||
|             # if bsuid == qqqbsuid: | ||||
|             #     breakpoint() | ||||
| 
 | ||||
|         for bs_mktid in list(pp_objs): | ||||
|             pp = pp_objs[bs_mktid] | ||||
|             pp.ensure_state() | ||||
| 
 | ||||
|             if ( | ||||
|  | @ -583,10 +598,10 @@ class PpTable(Struct): | |||
|                 # ignored; the closed positions won't be written to the | ||||
|                 # ``pps.toml`` since ``pp_active_entries`` above is what's | ||||
|                 # written. | ||||
|                 closed_pp_objs[bsuid] = pp | ||||
|                 closed_pp_objs[bs_mktid] = pp | ||||
| 
 | ||||
|             else: | ||||
|                 open_pp_objs[bsuid] = pp | ||||
|                 open_pp_objs[bs_mktid] = pp | ||||
| 
 | ||||
|         return open_pp_objs, closed_pp_objs | ||||
| 
 | ||||
|  | @ -600,7 +615,7 @@ class PpTable(Struct): | |||
|         # we don't store in the ``pps.toml``. | ||||
|         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 | ||||
|             # 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 | ||||
|     process any trade records into our normalized ``Transaction`` form | ||||
|     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 ( | ||||
|  | @ -690,9 +705,9 @@ def load_pps_from_ledger( | |||
| 
 | ||||
|         if filter_by: | ||||
|             records = {} | ||||
|             bsuids = set(filter_by) | ||||
|             bs_mktids = set(filter_by) | ||||
|             for tid, r in src_records.items(): | ||||
|                 if r.bsuid in bsuids: | ||||
|                 if r.bs_mktid in bs_mktids: | ||||
|                     records[tid] = r | ||||
|         else: | ||||
|             records = src_records | ||||
|  | @ -868,22 +883,35 @@ def open_pps( | |||
| 
 | ||||
|     # unmarshal/load ``pps.toml`` config entries into object form | ||||
|     # and update `PpTable` obj entries. | ||||
|     for fqsn, entry in pps.items(): | ||||
|         bsuid = entry['bsuid'] | ||||
|         symbol = Symbol.from_fqsn( | ||||
|             fqsn, | ||||
|     for fqme, entry in pps.items(): | ||||
| 
 | ||||
|             # NOTE & TODO: right now we fill in the defaults from | ||||
|             # `.data._source.Symbol` but eventually these should always | ||||
|             # either be already written to the pos table or provided at | ||||
|             # write time to ensure always having these values somewhere | ||||
|             # and thus allowing us to get our pos sizing precision | ||||
|             # correct! | ||||
|             info={ | ||||
|                 'asset_type': entry.get('asset_type', '<unknown>'), | ||||
|                 'price_tick_size': entry.get('price_tick_size', 0.01), | ||||
|                 'lot_tick_size': entry.get('lot_tick_size', 0.0), | ||||
|             } | ||||
|         # atype = entry.get('asset_type', '<unknown>') | ||||
| 
 | ||||
|         # unique broker market id | ||||
|         bs_mktid = ( | ||||
|             entry.get('bsuid') | ||||
|             or entry.get('bs_mktid') | ||||
|         ) | ||||
|         price_tick = ( | ||||
|             entry.get('price_tick_size') | ||||
|             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 | ||||
|  | @ -893,7 +921,7 @@ def open_pps( | |||
|         # index clears entries in "object" form by tid in a top | ||||
|         # level dict instead of a list (as is presented in our | ||||
|         # ``pps.toml``). | ||||
|         clears = pp_objs.setdefault(bsuid, {}) | ||||
|         clears = pp_objs.setdefault(bs_mktid, {}) | ||||
| 
 | ||||
|         # TODO: should be make a ``Struct`` for clear/event entries? | ||||
|         # convert "clear events table" from the toml config (list of | ||||
|  | @ -908,9 +936,9 @@ def open_pps( | |||
|             clears_table['dt'] = dt | ||||
| 
 | ||||
|             trans.append(Transaction( | ||||
|                 fqsn=bsuid, | ||||
|                 sym=symbol, | ||||
|                 bsuid=bsuid, | ||||
|                 fqsn=bs_mktid, | ||||
|                 sym=mkt, | ||||
|                 bs_mktid=bs_mktid, | ||||
|                 tid=tid, | ||||
|                 size=clears_table['size'], | ||||
|                 price=clears_table['price'], | ||||
|  | @ -933,13 +961,13 @@ def open_pps( | |||
|         if expiry: | ||||
|             expiry = pendulum.parse(expiry) | ||||
| 
 | ||||
|         pp = pp_objs[bsuid] = Position( | ||||
|             symbol, | ||||
|         pp = pp_objs[bs_mktid] = Position( | ||||
|             mkt, | ||||
|             size=size, | ||||
|             ppu=ppu, | ||||
|             split_ratio=split_ratio, | ||||
|             expiry=expiry, | ||||
|             bsuid=entry['bsuid'], | ||||
|             bs_mktid=bs_mktid, | ||||
|         ) | ||||
| 
 | ||||
|         # XXX: super critical, we need to be sure to include | ||||
|  |  | |||
|  | @ -127,7 +127,7 @@ your ``pps.toml`` file will have position entries like, | |||
|     [ib.algopaper."mnq.globex.20221216"] | ||||
|     size = -1.0 | ||||
|     ppu = 12423.630576923071 | ||||
|     bsuid = 515416577 | ||||
|     bs_mktid = 515416577 | ||||
|     expiry = "2022-12-16T00:00:00+00:00" | ||||
|     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" }, | ||||
|  |  | |||
|  | @ -335,12 +335,12 @@ async def update_and_audit_msgs( | |||
| 
 | ||||
|     msgs: list[BrokerdPosition] = [] | ||||
|     for p in pps: | ||||
|         bsuid = p.bsuid | ||||
|         bs_mktid = p.bs_mktid | ||||
| 
 | ||||
|         # retreive equivalent ib reported position message | ||||
|         # for comparison/audit versus the piker equivalent | ||||
|         # breakeven pp calcs. | ||||
|         ibppmsg = cids2pps.get((acctid, bsuid)) | ||||
|         ibppmsg = cids2pps.get((acctid, bs_mktid)) | ||||
| 
 | ||||
|         if ibppmsg: | ||||
|             msg = BrokerdPosition( | ||||
|  | @ -555,18 +555,18 @@ async def trades_dialogue( | |||
|                     # collect all ib-pp reported positions so that we can be | ||||
|                     # sure know which positions to update from the ledger if | ||||
|                     # 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 = acctid.strip('ib.') | ||||
|                     cids2pps[(acctid, bsuid)] = msg | ||||
|                     cids2pps[(acctid, bs_mktid)] = msg | ||||
|                     assert msg.account in accounts, ( | ||||
|                         f'Position for unknown account: {msg.account}') | ||||
| 
 | ||||
|                     ledger = ledgers[acctid] | ||||
|                     table = tables[acctid] | ||||
| 
 | ||||
|                     pp = table.pps.get(bsuid) | ||||
|                     pp = table.pps.get(bs_mktid) | ||||
|                     if ( | ||||
|                         not pp | ||||
|                         or pp.size != msg.size | ||||
|  | @ -605,12 +605,12 @@ async def trades_dialogue( | |||
|                         # the updated output (maybe this is a bug?) but | ||||
|                         # if you create a pos from TWS and then load it | ||||
|                         # from the api trades it seems we get a key | ||||
|                         # error from ``update[bsuid]`` ? | ||||
|                         pp = table.pps.get(bsuid) | ||||
|                         # error from ``update[bs_mktid]`` ? | ||||
|                         pp = table.pps.get(bs_mktid) | ||||
|                         if not pp: | ||||
|                             log.error( | ||||
|                                 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 ' | ||||
|                                 'for now.' | ||||
|                             ) | ||||
|  | @ -620,8 +620,8 @@ async def trades_dialogue( | |||
|                         # the updated output (maybe this is a bug?) but | ||||
|                         # if you create a pos from TWS and then load it | ||||
|                         # from the api trades it seems we get a key | ||||
|                         # error from ``update[bsuid]`` ? | ||||
|                         pp = table.pps[bsuid] | ||||
|                         # error from ``update[bs_mktid]`` ? | ||||
|                         pp = table.pps[bs_mktid] | ||||
|                         pairinfo = pp.symbol | ||||
|                         if msg.size != pp.size: | ||||
|                             log.error( | ||||
|  | @ -760,7 +760,7 @@ async def emit_pp_update( | |||
|     # re-formatted pps as msgs to the ems. | ||||
|     for pos in filter( | ||||
|         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( | ||||
|             acctid, | ||||
|  | @ -1225,7 +1225,7 @@ def norm_trade_records( | |||
|                 cost=comms, | ||||
|                 dt=dt, | ||||
|                 expiry=expiry, | ||||
|                 bsuid=conid, | ||||
|                 bs_mktid=conid, | ||||
|             ), | ||||
|             key=lambda t: t.dt | ||||
|         ) | ||||
|  |  | |||
|  | @ -206,7 +206,7 @@ class Allocator(Struct): | |||
|                     symbol=sym, | ||||
|                     size=order_size, | ||||
|                     ppu=price, | ||||
|                     bsuid=sym, | ||||
|                     bs_mktid=sym, | ||||
|                 ) | ||||
|             ) | ||||
| 
 | ||||
|  |  | |||
|  | @ -1,5 +1,5 @@ | |||
| # 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 | ||||
| # it under the terms of the GNU Affero General Public License as published by | ||||
|  | @ -258,7 +258,7 @@ class PaperBoi(Struct): | |||
|             price=price, | ||||
|             cost=0,  # TODO: cost model | ||||
|             dt=pendulum.from_timestamp(fill_time_s), | ||||
|             bsuid=key, | ||||
|             bs_mktid=key, | ||||
|         ) | ||||
| 
 | ||||
|         with ( | ||||
|  |  | |||
|  | @ -737,7 +737,7 @@ async def open_order_mode( | |||
|                 ppu=0, | ||||
| 
 | ||||
|                 # XXX: BLEH, do we care about this on the client side? | ||||
|                 bsuid=symbol, | ||||
|                 bs_mktid=symbol.key, | ||||
|             ) | ||||
| 
 | ||||
|             # allocator config | ||||
|  |  | |||
		Loading…
	
		Reference in New Issue