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
|
||||
from ib_insync.contract import (
|
||||
Contract,
|
||||
# Option,
|
||||
# Forex,
|
||||
)
|
||||
from ib_insync.order import (
|
||||
Trade,
|
||||
|
@ -357,11 +355,24 @@ async def update_and_audit_msgs(
|
|||
# presume we're at least not more in the shit then we
|
||||
# thought.
|
||||
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(
|
||||
f'POSITION MISMATCH ib <-> piker ledger:\n'
|
||||
f'ib: {ibppmsg}\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
|
||||
|
||||
|
@ -480,6 +491,7 @@ async def trades_dialogue(
|
|||
# sure know which positions to update from the ledger if
|
||||
# any are missing from the ``pps.toml``
|
||||
bsuid, msg = pack_position(pos)
|
||||
|
||||
acctid = msg.account = accounts_def.inverse[msg.account]
|
||||
acctid = acctid.strip('ib.')
|
||||
cids2pps[(acctid, bsuid)] = msg
|
||||
|
@ -493,7 +505,7 @@ async def trades_dialogue(
|
|||
or pp.size != msg.size
|
||||
):
|
||||
trans = norm_trade_records(ledger)
|
||||
updated = table.update_from_trans(trans)
|
||||
table.update_from_trans(trans)
|
||||
# update trades ledgers for all accounts from connected
|
||||
# api clients which report trades for **this session**.
|
||||
trades = await proxy.trades()
|
||||
|
@ -519,14 +531,22 @@ async def trades_dialogue(
|
|||
trans = trans_by_acct.get(acctid)
|
||||
if trans:
|
||||
table.update_from_trans(trans)
|
||||
updated = table.update_from_trans(trans)
|
||||
|
||||
# XXX: not sure exactly why it wouldn't be in
|
||||
# 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]
|
||||
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:
|
||||
log.error(
|
||||
'Position mismatch {pp.symbol.front_fqsn()}:\n'
|
||||
|
@ -670,6 +690,22 @@ async def emit_pp_update(
|
|||
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(
|
||||
|
||||
trade_event_stream: trio.MemoryReceiveChannel,
|
||||
|
|
19
piker/pp.py
19
piker/pp.py
|
@ -134,6 +134,8 @@ class Position(Struct):
|
|||
# unique backend symbol id
|
||||
bsuid: str
|
||||
|
||||
split_ratio: Optional[int] = None
|
||||
|
||||
# ordered record of known constituent trade messages
|
||||
clears: dict[
|
||||
Union[str, int, Status], # trade id
|
||||
|
@ -159,6 +161,9 @@ class Position(Struct):
|
|||
clears = d.pop('clears')
|
||||
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
|
||||
# listing venue here even when the backend isn't providing
|
||||
# it via the trades ledger..
|
||||
|
@ -384,12 +389,22 @@ class Position(Struct):
|
|||
asize_h.append(accum_size)
|
||||
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:
|
||||
size: float = 0
|
||||
for tid, entry in self.clears.items():
|
||||
size += entry['size']
|
||||
|
||||
if self.split_ratio is not None:
|
||||
size = round(size * self.split_ratio)
|
||||
|
||||
return size
|
||||
|
||||
def minimize_clears(
|
||||
|
@ -848,6 +863,7 @@ def open_pps(
|
|||
size = entry['size']
|
||||
# TODO: remove but, handle old field name for now
|
||||
ppu = entry.get('ppu', entry.get('be_price', 0))
|
||||
split_ratio = entry.get('split_ratio')
|
||||
|
||||
expiry = entry.get('expiry')
|
||||
if expiry:
|
||||
|
@ -857,6 +873,7 @@ def open_pps(
|
|||
Symbol.from_fqsn(fqsn, info={}),
|
||||
size=size,
|
||||
ppu=ppu,
|
||||
split_ratio=split_ratio,
|
||||
expiry=expiry,
|
||||
bsuid=entry['bsuid'],
|
||||
|
||||
|
|
Loading…
Reference in New Issue