First try mega-basic stock (reverse) split support with `ib` and `pps.toml`
parent
1f43f660fe
commit
9782107153
|
@ -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,
|
||||||
|
@ -364,11 +362,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
|
||||||
|
|
||||||
|
@ -532,6 +543,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
|
||||||
|
@ -571,14 +583,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)
|
||||||
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'
|
||||||
|
@ -730,8 +750,11 @@ async def emit_pp_update(
|
||||||
_statuses: dict[str, str] = {
|
_statuses: dict[str, str] = {
|
||||||
'cancelled': 'canceled',
|
'cancelled': 'canceled',
|
||||||
'submitted': 'open',
|
'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',
|
# 'pendingsubmit': 'pending',
|
||||||
# 'filled': 'fill',
|
|
||||||
|
|
||||||
# TODO: see a current ``ib_insync`` issue around this:
|
# TODO: see a current ``ib_insync`` issue around this:
|
||||||
# https://github.com/erdewit/ib_insync/issues/363
|
# https://github.com/erdewit/ib_insync/issues/363
|
||||||
|
|
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