First try mega-basic stock (reverse) split support with `ib` and `pps.toml`
							parent
							
								
									6856ca207f
								
							
						
					
					
						commit
						7bec989eed
					
				| 
						 | 
					@ -36,8 +36,6 @@ from trio_typing import TaskStatus
 | 
				
			||||||
import tractor
 | 
					import tractor
 | 
				
			||||||
from ib_insync.contract import (
 | 
					from ib_insync.contract import (
 | 
				
			||||||
    Contract,
 | 
					    Contract,
 | 
				
			||||||
    # Option,
 | 
					 | 
				
			||||||
    # Forex,
 | 
					 | 
				
			||||||
)
 | 
					)
 | 
				
			||||||
from ib_insync.order import (
 | 
					from ib_insync.order import (
 | 
				
			||||||
    Trade,
 | 
					    Trade,
 | 
				
			||||||
| 
						 | 
					@ -357,11 +355,24 @@ async def update_and_audit_msgs(
 | 
				
			||||||
                # presume we're at least not more in the shit then we
 | 
					                # presume we're at least not more in the shit then we
 | 
				
			||||||
                # thought.
 | 
					                # thought.
 | 
				
			||||||
                if diff:
 | 
					                if diff:
 | 
				
			||||||
 | 
					                    reverse_split_ratio = pikersize / ibsize
 | 
				
			||||||
 | 
					                    split_ratio = 1/reverse_split_ratio
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                    if split_ratio >= reverse_split_ratio:
 | 
				
			||||||
 | 
					                        entry = f'split_ratio = {int(split_ratio)}'
 | 
				
			||||||
 | 
					                    else:
 | 
				
			||||||
 | 
					                        entry = f'split_ratio = 1/{int(reverse_split_ratio)}'
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                    raise ValueError(
 | 
					                    raise ValueError(
 | 
				
			||||||
                        f'POSITION MISMATCH ib <-> piker ledger:\n'
 | 
					                        f'POSITION MISMATCH ib <-> piker ledger:\n'
 | 
				
			||||||
                        f'ib: {ibppmsg}\n'
 | 
					                        f'ib: {ibppmsg}\n'
 | 
				
			||||||
                        f'piker: {msg}\n'
 | 
					                        f'piker: {msg}\n'
 | 
				
			||||||
                        'YOU SHOULD FIGURE OUT WHY TF YOUR LEDGER IS OFF!?!?'
 | 
					                        f'reverse_split_ratio: {reverse_split_ratio}\n'
 | 
				
			||||||
 | 
					                        f'split_ratio: {split_ratio}\n\n'
 | 
				
			||||||
 | 
					                        'FIGURE OUT WHY TF YOUR LEDGER IS OFF!?!?\n\n'
 | 
				
			||||||
 | 
					                        'If you are expecting a (reverse) split in this '
 | 
				
			||||||
 | 
					                        'instrument you should probably put the following '
 | 
				
			||||||
 | 
					                        f'in the `pps.toml` section:\n{entry}'
 | 
				
			||||||
                    )
 | 
					                    )
 | 
				
			||||||
                    msg.size = ibsize
 | 
					                    msg.size = ibsize
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -480,6 +491,7 @@ async def trades_dialogue(
 | 
				
			||||||
                    # 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)
 | 
					                    bsuid, 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, bsuid)] = msg
 | 
				
			||||||
| 
						 | 
					@ -493,7 +505,7 @@ async def trades_dialogue(
 | 
				
			||||||
                        or pp.size != msg.size
 | 
					                        or pp.size != msg.size
 | 
				
			||||||
                    ):
 | 
					                    ):
 | 
				
			||||||
                        trans = norm_trade_records(ledger)
 | 
					                        trans = norm_trade_records(ledger)
 | 
				
			||||||
                        updated = table.update_from_trans(trans)
 | 
					                        table.update_from_trans(trans)
 | 
				
			||||||
                        # update trades ledgers for all accounts from connected
 | 
					                        # update trades ledgers for all accounts from connected
 | 
				
			||||||
                        # api clients which report trades for **this session**.
 | 
					                        # api clients which report trades for **this session**.
 | 
				
			||||||
                        trades = await proxy.trades()
 | 
					                        trades = await proxy.trades()
 | 
				
			||||||
| 
						 | 
					@ -519,14 +531,22 @@ async def trades_dialogue(
 | 
				
			||||||
                            trans = trans_by_acct.get(acctid)
 | 
					                            trans = trans_by_acct.get(acctid)
 | 
				
			||||||
                            if trans:
 | 
					                            if trans:
 | 
				
			||||||
                                table.update_from_trans(trans)
 | 
					                                table.update_from_trans(trans)
 | 
				
			||||||
                                updated = table.update_from_trans(trans)
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
                        # XXX: not sure exactly why it wouldn't be in
 | 
					                        # XXX: not sure exactly why it wouldn't be in
 | 
				
			||||||
                        # 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[bsuid]`` ?
 | 
				
			||||||
                        pp = table.pps[bsuid]
 | 
					                        pp = table.pps.get(bsuid)
 | 
				
			||||||
 | 
					                        if not pp:
 | 
				
			||||||
 | 
					                            log.error(
 | 
				
			||||||
 | 
					                                f'The contract id for {msg} may have '
 | 
				
			||||||
 | 
					                                f'changed to {bsuid}\nYou may need to '
 | 
				
			||||||
 | 
					                                'adjust your ledger for this, skipping '
 | 
				
			||||||
 | 
					                                'for now.'
 | 
				
			||||||
 | 
					                            )
 | 
				
			||||||
 | 
					                            continue
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                        if msg.size != pp.size:
 | 
					                        if msg.size != pp.size:
 | 
				
			||||||
                            log.error(
 | 
					                            log.error(
 | 
				
			||||||
                                'Position mismatch {pp.symbol.front_fqsn()}:\n'
 | 
					                                'Position mismatch {pp.symbol.front_fqsn()}:\n'
 | 
				
			||||||
| 
						 | 
					@ -670,6 +690,22 @@ async def emit_pp_update(
 | 
				
			||||||
    await ems_stream.send(msg)
 | 
					    await ems_stream.send(msg)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					_statuses: dict[str, str] = {
 | 
				
			||||||
 | 
					    'cancelled': 'canceled',
 | 
				
			||||||
 | 
					    'submitted': 'open',
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    # XXX: just pass these through? it duplicates actual fill events other
 | 
				
			||||||
 | 
					    # then the case where you the `.remaining == 0` case which is our
 | 
				
			||||||
 | 
					    # 'closed'` case.
 | 
				
			||||||
 | 
					    # 'filled': 'pending',
 | 
				
			||||||
 | 
					    # 'pendingsubmit': 'pending',
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    # TODO: see a current ``ib_insync`` issue around this:
 | 
				
			||||||
 | 
					    # https://github.com/erdewit/ib_insync/issues/363
 | 
				
			||||||
 | 
					    'inactive': 'pending',
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
async def deliver_trade_events(
 | 
					async def deliver_trade_events(
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    trade_event_stream: trio.MemoryReceiveChannel,
 | 
					    trade_event_stream: trio.MemoryReceiveChannel,
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
							
								
								
									
										19
									
								
								piker/pp.py
								
								
								
								
							
							
						
						
									
										19
									
								
								piker/pp.py
								
								
								
								
							| 
						 | 
					@ -134,6 +134,8 @@ class Position(Struct):
 | 
				
			||||||
    # unique backend symbol id
 | 
					    # unique backend symbol id
 | 
				
			||||||
    bsuid: str
 | 
					    bsuid: str
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    split_ratio: Optional[int] = 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
 | 
				
			||||||
| 
						 | 
					@ -159,6 +161,9 @@ class Position(Struct):
 | 
				
			||||||
        clears = d.pop('clears')
 | 
					        clears = d.pop('clears')
 | 
				
			||||||
        expiry = d.pop('expiry')
 | 
					        expiry = d.pop('expiry')
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        if self.split_ratio is None:
 | 
				
			||||||
 | 
					            d.pop('split_ratio')
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        # TODO: we need to figure out how to have one top level
 | 
					        # TODO: we need to figure out how to have one top level
 | 
				
			||||||
        # listing venue here even when the backend isn't providing
 | 
					        # listing venue here even when the backend isn't providing
 | 
				
			||||||
        # it via the trades ledger..
 | 
					        # it via the trades ledger..
 | 
				
			||||||
| 
						 | 
					@ -384,12 +389,22 @@ class Position(Struct):
 | 
				
			||||||
                asize_h.append(accum_size)
 | 
					                asize_h.append(accum_size)
 | 
				
			||||||
                ppu_h.append(ppu_h[-1])
 | 
					                ppu_h.append(ppu_h[-1])
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        return ppu_h[-1] if ppu_h else 0
 | 
					        final_ppu = ppu_h[-1] if ppu_h else 0
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        # handle any split info entered (for now) manually by user
 | 
				
			||||||
 | 
					        if self.split_ratio is not None:
 | 
				
			||||||
 | 
					            final_ppu /= self.split_ratio
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        return final_ppu
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def calc_size(self) -> float:
 | 
					    def calc_size(self) -> float:
 | 
				
			||||||
        size: float = 0
 | 
					        size: float = 0
 | 
				
			||||||
        for tid, entry in self.clears.items():
 | 
					        for tid, entry in self.clears.items():
 | 
				
			||||||
            size += entry['size']
 | 
					            size += entry['size']
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        if self.split_ratio is not None:
 | 
				
			||||||
 | 
					            size = round(size * self.split_ratio)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        return size
 | 
					        return size
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def minimize_clears(
 | 
					    def minimize_clears(
 | 
				
			||||||
| 
						 | 
					@ -848,6 +863,7 @@ def open_pps(
 | 
				
			||||||
        size = entry['size']
 | 
					        size = entry['size']
 | 
				
			||||||
        # TODO: remove but, handle old field name for now
 | 
					        # TODO: remove but, handle old field name for now
 | 
				
			||||||
        ppu = entry.get('ppu', entry.get('be_price', 0))
 | 
					        ppu = entry.get('ppu', entry.get('be_price', 0))
 | 
				
			||||||
 | 
					        split_ratio = entry.get('split_ratio')
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        expiry = entry.get('expiry')
 | 
					        expiry = entry.get('expiry')
 | 
				
			||||||
        if expiry:
 | 
					        if expiry:
 | 
				
			||||||
| 
						 | 
					@ -857,6 +873,7 @@ def open_pps(
 | 
				
			||||||
            Symbol.from_fqsn(fqsn, info={}),
 | 
					            Symbol.from_fqsn(fqsn, info={}),
 | 
				
			||||||
            size=size,
 | 
					            size=size,
 | 
				
			||||||
            ppu=ppu,
 | 
					            ppu=ppu,
 | 
				
			||||||
 | 
					            split_ratio=split_ratio,
 | 
				
			||||||
            expiry=expiry,
 | 
					            expiry=expiry,
 | 
				
			||||||
            bsuid=entry['bsuid'],
 | 
					            bsuid=entry['bsuid'],
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
		Loading…
	
		Reference in New Issue