Merge pull request #470 from pikers/decimalization_take_2
Fixed float dust bug on zero positionxdo_and_you
commit
201f86e482
|
@ -70,7 +70,10 @@ from piker.clearing._messages import (
|
||||||
BrokerdFill,
|
BrokerdFill,
|
||||||
BrokerdError,
|
BrokerdError,
|
||||||
)
|
)
|
||||||
from piker.data._source import Symbol
|
from piker.data._source import (
|
||||||
|
Symbol,
|
||||||
|
float_digits,
|
||||||
|
)
|
||||||
from .api import (
|
from .api import (
|
||||||
_accounts2clients,
|
_accounts2clients,
|
||||||
con2fqsn,
|
con2fqsn,
|
||||||
|
@ -304,6 +307,9 @@ async def update_ledger_from_api_trades(
|
||||||
|
|
||||||
entry['listingExchange'] = pexch
|
entry['listingExchange'] = pexch
|
||||||
|
|
||||||
|
# pack in the ``Contract.secType``
|
||||||
|
entry['asset_type'] = condict['secType']
|
||||||
|
|
||||||
conf = get_config()
|
conf = get_config()
|
||||||
entries = api_trades_to_ledger_entries(
|
entries = api_trades_to_ledger_entries(
|
||||||
conf['accounts'].inverse,
|
conf['accounts'].inverse,
|
||||||
|
@ -616,9 +622,10 @@ async def trades_dialogue(
|
||||||
# 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[bsuid]
|
||||||
|
pairinfo = pp.symbol
|
||||||
if msg.size != pp.size:
|
if msg.size != pp.size:
|
||||||
log.error(
|
log.error(
|
||||||
f'Position mismatch {pp.symbol.front_fqsn()}:\n'
|
f'Pos size mismatch {pairinfo.front_fqsn()}:\n'
|
||||||
f'ib: {msg.size}\n'
|
f'ib: {msg.size}\n'
|
||||||
f'piker: {pp.size}\n'
|
f'piker: {pp.size}\n'
|
||||||
)
|
)
|
||||||
|
@ -1095,13 +1102,15 @@ def norm_trade_records(
|
||||||
|
|
||||||
'''
|
'''
|
||||||
records: list[Transaction] = []
|
records: list[Transaction] = []
|
||||||
for tid, record in ledger.items():
|
|
||||||
|
|
||||||
|
for tid, record in ledger.items():
|
||||||
conid = record.get('conId') or record['conid']
|
conid = record.get('conId') or record['conid']
|
||||||
comms = record.get('commission')
|
comms = record.get('commission')
|
||||||
if comms is None:
|
if comms is None:
|
||||||
comms = -1*record['ibCommission']
|
comms = -1*record['ibCommission']
|
||||||
|
|
||||||
price = record.get('price') or record['tradePrice']
|
price = record.get('price') or record['tradePrice']
|
||||||
|
price_tick_digits = float_digits(price)
|
||||||
|
|
||||||
# the api doesn't do the -/+ on the quantity for you but flex
|
# the api doesn't do the -/+ on the quantity for you but flex
|
||||||
# records do.. are you fucking serious ib...!?
|
# records do.. are you fucking serious ib...!?
|
||||||
|
@ -1144,9 +1153,14 @@ def norm_trade_records(
|
||||||
|
|
||||||
# special handling of symbol extraction from
|
# special handling of symbol extraction from
|
||||||
# flex records using some ad-hoc schema parsing.
|
# flex records using some ad-hoc schema parsing.
|
||||||
instr = record.get('assetCategory')
|
asset_type: str = record.get('assetCategory') or record['secType']
|
||||||
if instr == 'FUT':
|
|
||||||
symbol = record['description'][:3]
|
# TODO: XXX: WOA this is kinda hacky.. probably
|
||||||
|
# should figure out the correct future pair key more
|
||||||
|
# explicitly and consistently?
|
||||||
|
if asset_type == 'FUT':
|
||||||
|
# (flex) ledger entries don't have any simple 3-char key?
|
||||||
|
symbol = record['symbol'][:3]
|
||||||
|
|
||||||
# try to build out piker fqsn from record.
|
# try to build out piker fqsn from record.
|
||||||
expiry = record.get(
|
expiry = record.get(
|
||||||
|
@ -1156,10 +1170,34 @@ def norm_trade_records(
|
||||||
suffix = f'{exch}.{expiry}'
|
suffix = f'{exch}.{expiry}'
|
||||||
expiry = pendulum.parse(expiry)
|
expiry = pendulum.parse(expiry)
|
||||||
|
|
||||||
fqsn = Symbol.from_fqsn(
|
src: str = record['currency']
|
||||||
|
|
||||||
|
pair = Symbol.from_fqsn(
|
||||||
fqsn=f'{symbol}.{suffix}.ib',
|
fqsn=f'{symbol}.{suffix}.ib',
|
||||||
info={},
|
info={
|
||||||
).front_fqsn().rstrip('.ib')
|
'tick_size_digits': price_tick_digits,
|
||||||
|
|
||||||
|
# NOTE: for "legacy" assets, volume is normally discreet, not
|
||||||
|
# a float, but we keep a digit in case the suitz decide
|
||||||
|
# to get crazy and change it; we'll be kinda ready
|
||||||
|
# schema-wise..
|
||||||
|
'lot_size_digits': 1,
|
||||||
|
|
||||||
|
# TODO: remove when we switching from
|
||||||
|
# ``Symbol`` -> ``MktPair``
|
||||||
|
'asset_type': asset_type,
|
||||||
|
|
||||||
|
# TODO: figure out a target fin-type name
|
||||||
|
# set and normalize to that here!
|
||||||
|
'dst_type': asset_type.lower(),
|
||||||
|
|
||||||
|
# starting to use new key naming as in ``MktPair``
|
||||||
|
# type have drafted...
|
||||||
|
'src': src,
|
||||||
|
'src_type': 'fiat',
|
||||||
|
},
|
||||||
|
)
|
||||||
|
fqsn = pair.front_fqsn().rstrip('.ib')
|
||||||
|
|
||||||
# NOTE: for flex records the normal fields for defining an fqsn
|
# NOTE: for flex records the normal fields for defining an fqsn
|
||||||
# sometimes won't be available so we rely on two approaches for
|
# sometimes won't be available so we rely on two approaches for
|
||||||
|
@ -1175,6 +1213,7 @@ def norm_trade_records(
|
||||||
records,
|
records,
|
||||||
Transaction(
|
Transaction(
|
||||||
fqsn=fqsn,
|
fqsn=fqsn,
|
||||||
|
sym=pair,
|
||||||
tid=tid,
|
tid=tid,
|
||||||
size=size,
|
size=size,
|
||||||
price=price,
|
price=price,
|
||||||
|
@ -1201,7 +1240,11 @@ def parse_flex_dt(
|
||||||
|
|
||||||
def api_trades_to_ledger_entries(
|
def api_trades_to_ledger_entries(
|
||||||
accounts: bidict,
|
accounts: bidict,
|
||||||
trade_entries: list[object],
|
|
||||||
|
# TODO: maybe we should just be passing through the
|
||||||
|
# ``ib_insync.order.Trade`` instance directly here
|
||||||
|
# instead of pre-casting to dicts?
|
||||||
|
trade_entries: list[dict],
|
||||||
|
|
||||||
) -> dict:
|
) -> dict:
|
||||||
'''
|
'''
|
||||||
|
|
|
@ -770,7 +770,7 @@ async def stream_quotes(
|
||||||
|
|
||||||
syminfo['price_tick_size'] = max(syminfo['minTick'], min_tick)
|
syminfo['price_tick_size'] = max(syminfo['minTick'], min_tick)
|
||||||
|
|
||||||
# for "traditional" assets, volume is normally discreet, not
|
# for "legacy" assets, volume is normally discreet, not
|
||||||
# a float
|
# a float
|
||||||
syminfo['lot_tick_size'] = 0.0
|
syminfo['lot_tick_size'] = 0.0
|
||||||
|
|
||||||
|
|
|
@ -40,6 +40,8 @@ import base64
|
||||||
import trio
|
import trio
|
||||||
|
|
||||||
from piker import config
|
from piker import config
|
||||||
|
from piker.data.types import Struct
|
||||||
|
from piker.data._source import Symbol
|
||||||
from piker.brokers._util import (
|
from piker.brokers._util import (
|
||||||
resproc,
|
resproc,
|
||||||
SymbolNotFound,
|
SymbolNotFound,
|
||||||
|
@ -113,11 +115,53 @@ class InvalidKey(ValueError):
|
||||||
'''
|
'''
|
||||||
|
|
||||||
|
|
||||||
|
# https://www.kraken.com/features/api#get-tradable-pairs
|
||||||
|
class Pair(Struct):
|
||||||
|
altname: str # alternate pair name
|
||||||
|
wsname: str # WebSocket pair name (if available)
|
||||||
|
aclass_base: str # asset class of base component
|
||||||
|
base: str # asset id of base component
|
||||||
|
aclass_quote: str # asset class of quote component
|
||||||
|
quote: str # asset id of quote component
|
||||||
|
lot: str # volume lot size
|
||||||
|
|
||||||
|
cost_decimals: int
|
||||||
|
costmin: float
|
||||||
|
pair_decimals: int # scaling decimal places for pair
|
||||||
|
lot_decimals: int # scaling decimal places for volume
|
||||||
|
|
||||||
|
# amount to multiply lot volume by to get currency volume
|
||||||
|
lot_multiplier: float
|
||||||
|
|
||||||
|
# array of leverage amounts available when buying
|
||||||
|
leverage_buy: list[int]
|
||||||
|
# array of leverage amounts available when selling
|
||||||
|
leverage_sell: list[int]
|
||||||
|
|
||||||
|
# fee schedule array in [volume, percent fee] tuples
|
||||||
|
fees: list[tuple[int, float]]
|
||||||
|
|
||||||
|
# maker fee schedule array in [volume, percent fee] tuples (if on
|
||||||
|
# maker/taker)
|
||||||
|
fees_maker: list[tuple[int, float]]
|
||||||
|
|
||||||
|
fee_volume_currency: str # volume discount currency
|
||||||
|
margin_call: str # margin call level
|
||||||
|
margin_stop: str # stop-out/liquidation margin level
|
||||||
|
ordermin: float # minimum order volume for pair
|
||||||
|
tick_size: float # min price step size
|
||||||
|
status: str
|
||||||
|
|
||||||
|
short_position_limit: float = 0
|
||||||
|
long_position_limit: float = float('inf')
|
||||||
|
|
||||||
|
|
||||||
class Client:
|
class Client:
|
||||||
|
|
||||||
# global symbol normalization table
|
# global symbol normalization table
|
||||||
_ntable: dict[str, str] = {}
|
_ntable: dict[str, str] = {}
|
||||||
_atable: bidict[str, str] = bidict()
|
_atable: bidict[str, str] = bidict()
|
||||||
|
_pairs: dict[str, Pair] = {}
|
||||||
|
|
||||||
def __init__(
|
def __init__(
|
||||||
self,
|
self,
|
||||||
|
@ -133,13 +177,12 @@ class Client:
|
||||||
'krakenex/2.1.0 (+https://github.com/veox/python3-krakenex)'
|
'krakenex/2.1.0 (+https://github.com/veox/python3-krakenex)'
|
||||||
})
|
})
|
||||||
self.conf: dict[str, str] = config
|
self.conf: dict[str, str] = config
|
||||||
self._pairs: list[str] = []
|
|
||||||
self._name = name
|
self._name = name
|
||||||
self._api_key = api_key
|
self._api_key = api_key
|
||||||
self._secret = secret
|
self._secret = secret
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def pairs(self) -> dict[str, Any]:
|
def pairs(self) -> dict[str, Pair]:
|
||||||
if self._pairs is None:
|
if self._pairs is None:
|
||||||
raise RuntimeError(
|
raise RuntimeError(
|
||||||
"Make sure to run `cache_symbols()` on startup!"
|
"Make sure to run `cache_symbols()` on startup!"
|
||||||
|
@ -295,15 +338,28 @@ class Client:
|
||||||
|
|
||||||
trans: dict[str, Transaction] = {}
|
trans: dict[str, Transaction] = {}
|
||||||
for entry in xfers:
|
for entry in xfers:
|
||||||
# look up the normalized name
|
|
||||||
asset = self._atable[entry['asset']].lower()
|
# look up the normalized name and asset info
|
||||||
|
asset_key = entry['asset']
|
||||||
|
asset_info = self.assets[asset_key]
|
||||||
|
asset = self._atable[asset_key].lower()
|
||||||
|
|
||||||
# XXX: this is in the asset units (likely) so it isn't
|
# XXX: this is in the asset units (likely) so it isn't
|
||||||
# quite the same as a commisions cost necessarily..)
|
# quite the same as a commisions cost necessarily..)
|
||||||
cost = float(entry['fee'])
|
cost = float(entry['fee'])
|
||||||
|
|
||||||
|
fqsn = asset + '.kraken'
|
||||||
|
pairinfo = Symbol.from_fqsn(
|
||||||
|
fqsn,
|
||||||
|
info={
|
||||||
|
'asset_type': 'crypto',
|
||||||
|
'lot_tick_size': asset_info['decimals'],
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
tran = Transaction(
|
tran = Transaction(
|
||||||
fqsn=asset + '.kraken',
|
fqsn=fqsn,
|
||||||
|
sym=pairinfo,
|
||||||
tid=entry['txid'],
|
tid=entry['txid'],
|
||||||
dt=pendulum.from_timestamp(entry['time']),
|
dt=pendulum.from_timestamp(entry['time']),
|
||||||
bsuid=f'{asset}{src_asset}',
|
bsuid=f'{asset}{src_asset}',
|
||||||
|
@ -317,7 +373,7 @@ class Client:
|
||||||
price='NaN',
|
price='NaN',
|
||||||
|
|
||||||
# XXX: see note above
|
# XXX: see note above
|
||||||
cost=0,
|
cost=cost,
|
||||||
)
|
)
|
||||||
trans[tran.tid] = tran
|
trans[tran.tid] = tran
|
||||||
|
|
||||||
|
@ -372,7 +428,7 @@ class Client:
|
||||||
self,
|
self,
|
||||||
pair: Optional[str] = None,
|
pair: Optional[str] = None,
|
||||||
|
|
||||||
) -> dict[str, dict[str, str]]:
|
) -> dict[str, Pair] | Pair:
|
||||||
|
|
||||||
if pair is not None:
|
if pair is not None:
|
||||||
pairs = {'pair': pair}
|
pairs = {'pair': pair}
|
||||||
|
@ -389,19 +445,36 @@ class Client:
|
||||||
|
|
||||||
if pair is not None:
|
if pair is not None:
|
||||||
_, data = next(iter(pairs.items()))
|
_, data = next(iter(pairs.items()))
|
||||||
return data
|
return Pair(**data)
|
||||||
else:
|
else:
|
||||||
return pairs
|
return {key: Pair(**data) for key, data in pairs.items()}
|
||||||
|
|
||||||
async def cache_symbols(
|
async def cache_symbols(self) -> dict:
|
||||||
self,
|
'''
|
||||||
) -> dict:
|
Load all market pair info build and cache it for downstream use.
|
||||||
|
|
||||||
|
A ``._ntable: dict[str, str]`` is available for mapping the
|
||||||
|
websocket pair name-keys and their http endpoint API (smh)
|
||||||
|
equivalents to the "alternative name" which is generally the one
|
||||||
|
we actually want to use XD
|
||||||
|
|
||||||
|
'''
|
||||||
if not self._pairs:
|
if not self._pairs:
|
||||||
self._pairs = await self.symbol_info()
|
self._pairs.update(await self.symbol_info())
|
||||||
|
|
||||||
ntable = {}
|
# table of all ws and rest keys to their alt-name values.
|
||||||
for restapikey, info in self._pairs.items():
|
ntable: dict[str, str] = {}
|
||||||
ntable[restapikey] = ntable[info['wsname']] = info['altname']
|
|
||||||
|
for rest_key in list(self._pairs.keys()):
|
||||||
|
|
||||||
|
pair: Pair = self._pairs[rest_key]
|
||||||
|
altname = pair.altname
|
||||||
|
wsname = pair.wsname
|
||||||
|
ntable[rest_key] = ntable[wsname] = altname
|
||||||
|
|
||||||
|
# register the pair under all monikers, a giant flat
|
||||||
|
# surjection of all possible names to each info obj.
|
||||||
|
self._pairs[altname] = self._pairs[wsname] = pair
|
||||||
|
|
||||||
self._ntable.update(ntable)
|
self._ntable.update(ntable)
|
||||||
|
|
||||||
|
@ -411,26 +484,34 @@ class Client:
|
||||||
self,
|
self,
|
||||||
pattern: str,
|
pattern: str,
|
||||||
limit: int = None,
|
limit: int = None,
|
||||||
|
|
||||||
) -> dict[str, Any]:
|
) -> dict[str, Any]:
|
||||||
if self._pairs is not None:
|
'''
|
||||||
data = self._pairs
|
Search for a symbol by "alt name"..
|
||||||
else:
|
|
||||||
data = await self.symbol_info()
|
It is expected that the ``Client._pairs`` table
|
||||||
|
gets populated before conducting the underlying fuzzy-search
|
||||||
|
over the pair-key set.
|
||||||
|
|
||||||
|
'''
|
||||||
|
if not len(self._pairs):
|
||||||
|
await self.cache_symbols()
|
||||||
|
assert self._pairs, '`Client.cache_symbols()` was never called!?'
|
||||||
|
|
||||||
matches = fuzzy.extractBests(
|
matches = fuzzy.extractBests(
|
||||||
pattern,
|
pattern,
|
||||||
data,
|
self._pairs,
|
||||||
score_cutoff=50,
|
score_cutoff=50,
|
||||||
)
|
)
|
||||||
# repack in dict form
|
# repack in dict form
|
||||||
return {item[0]['altname']: item[0] for item in matches}
|
return {item[0].altname: item[0] for item in matches}
|
||||||
|
|
||||||
async def bars(
|
async def bars(
|
||||||
self,
|
self,
|
||||||
symbol: str = 'XBTUSD',
|
symbol: str = 'XBTUSD',
|
||||||
|
|
||||||
# UTC 2017-07-02 12:53:20
|
# UTC 2017-07-02 12:53:20
|
||||||
since: Optional[Union[int, datetime]] = None,
|
since: Union[int, datetime] | None = None,
|
||||||
count: int = 720, # <- max allowed per query
|
count: int = 720, # <- max allowed per query
|
||||||
as_np: bool = True,
|
as_np: bool = True,
|
||||||
|
|
||||||
|
@ -506,7 +587,7 @@ class Client:
|
||||||
def normalize_symbol(
|
def normalize_symbol(
|
||||||
cls,
|
cls,
|
||||||
ticker: str
|
ticker: str
|
||||||
) -> str:
|
) -> tuple[str, Pair]:
|
||||||
'''
|
'''
|
||||||
Normalize symbol names to to a 3x3 pair from the global
|
Normalize symbol names to to a 3x3 pair from the global
|
||||||
definition map which we build out from the data retreived from
|
definition map which we build out from the data retreived from
|
||||||
|
@ -514,7 +595,7 @@ class Client:
|
||||||
|
|
||||||
'''
|
'''
|
||||||
ticker = cls._ntable[ticker]
|
ticker = cls._ntable[ticker]
|
||||||
return ticker.lower()
|
return ticker.lower(), cls._pairs[ticker]
|
||||||
|
|
||||||
|
|
||||||
@acm
|
@acm
|
||||||
|
|
|
@ -48,6 +48,7 @@ from piker.pp import (
|
||||||
open_trade_ledger,
|
open_trade_ledger,
|
||||||
open_pps,
|
open_pps,
|
||||||
)
|
)
|
||||||
|
from piker.data._source import Symbol
|
||||||
from piker.clearing._messages import (
|
from piker.clearing._messages import (
|
||||||
Order,
|
Order,
|
||||||
Status,
|
Status,
|
||||||
|
@ -469,13 +470,12 @@ async def trades_dialogue(
|
||||||
with (
|
with (
|
||||||
open_pps(
|
open_pps(
|
||||||
'kraken',
|
'kraken',
|
||||||
acctid,
|
acctid
|
||||||
write_on_exit=True,
|
|
||||||
) as table,
|
) as table,
|
||||||
|
|
||||||
open_trade_ledger(
|
open_trade_ledger(
|
||||||
'kraken',
|
'kraken',
|
||||||
acctid,
|
acctid
|
||||||
) as ledger_dict,
|
) as ledger_dict,
|
||||||
):
|
):
|
||||||
# transaction-ify the ledger entries
|
# transaction-ify the ledger entries
|
||||||
|
@ -1197,10 +1197,21 @@ def norm_trade_records(
|
||||||
}[record['type']]
|
}[record['type']]
|
||||||
|
|
||||||
# we normalize to kraken's `altname` always..
|
# we normalize to kraken's `altname` always..
|
||||||
bsuid = norm_sym = Client.normalize_symbol(record['pair'])
|
bsuid, pair_info = Client.normalize_symbol(record['pair'])
|
||||||
|
fqsn = f'{bsuid}.kraken'
|
||||||
|
|
||||||
|
mktpair = Symbol.from_fqsn(
|
||||||
|
fqsn,
|
||||||
|
info={
|
||||||
|
'lot_size_digits': pair_info.lot_decimals,
|
||||||
|
'tick_size_digits': pair_info.pair_decimals,
|
||||||
|
'asset_type': 'crypto',
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
records[tid] = Transaction(
|
records[tid] = Transaction(
|
||||||
fqsn=f'{norm_sym}.kraken',
|
fqsn=fqsn,
|
||||||
|
sym=mktpair,
|
||||||
tid=tid,
|
tid=tid,
|
||||||
size=size,
|
size=size,
|
||||||
price=float(record['price']),
|
price=float(record['price']),
|
||||||
|
|
|
@ -42,56 +42,15 @@ from piker.brokers._util import (
|
||||||
DataUnavailable,
|
DataUnavailable,
|
||||||
)
|
)
|
||||||
from piker.log import get_console_log
|
from piker.log import get_console_log
|
||||||
from piker.data import ShmArray
|
|
||||||
from piker.data.types import Struct
|
from piker.data.types import Struct
|
||||||
from piker.data._web_bs import open_autorecon_ws, NoBsWs
|
from piker.data._web_bs import open_autorecon_ws, NoBsWs
|
||||||
from . import log
|
from . import log
|
||||||
from .api import (
|
from .api import (
|
||||||
Client,
|
Client,
|
||||||
|
Pair,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
# https://www.kraken.com/features/api#get-tradable-pairs
|
|
||||||
class Pair(Struct):
|
|
||||||
altname: str # alternate pair name
|
|
||||||
wsname: str # WebSocket pair name (if available)
|
|
||||||
aclass_base: str # asset class of base component
|
|
||||||
base: str # asset id of base component
|
|
||||||
aclass_quote: str # asset class of quote component
|
|
||||||
quote: str # asset id of quote component
|
|
||||||
lot: str # volume lot size
|
|
||||||
|
|
||||||
cost_decimals: int
|
|
||||||
costmin: float
|
|
||||||
pair_decimals: int # scaling decimal places for pair
|
|
||||||
lot_decimals: int # scaling decimal places for volume
|
|
||||||
|
|
||||||
# amount to multiply lot volume by to get currency volume
|
|
||||||
lot_multiplier: float
|
|
||||||
|
|
||||||
# array of leverage amounts available when buying
|
|
||||||
leverage_buy: list[int]
|
|
||||||
# array of leverage amounts available when selling
|
|
||||||
leverage_sell: list[int]
|
|
||||||
|
|
||||||
# fee schedule array in [volume, percent fee] tuples
|
|
||||||
fees: list[tuple[int, float]]
|
|
||||||
|
|
||||||
# maker fee schedule array in [volume, percent fee] tuples (if on
|
|
||||||
# maker/taker)
|
|
||||||
fees_maker: list[tuple[int, float]]
|
|
||||||
|
|
||||||
fee_volume_currency: str # volume discount currency
|
|
||||||
margin_call: str # margin call level
|
|
||||||
margin_stop: str # stop-out/liquidation margin level
|
|
||||||
ordermin: float # minimum order volume for pair
|
|
||||||
tick_size: float # min price step size
|
|
||||||
status: str
|
|
||||||
|
|
||||||
short_position_limit: float
|
|
||||||
long_position_limit: float
|
|
||||||
|
|
||||||
|
|
||||||
class OHLC(Struct):
|
class OHLC(Struct):
|
||||||
'''
|
'''
|
||||||
Description of the flattened OHLC quote format.
|
Description of the flattened OHLC quote format.
|
||||||
|
@ -336,17 +295,17 @@ async def stream_quotes(
|
||||||
|
|
||||||
# transform to upper since piker style is always lower
|
# transform to upper since piker style is always lower
|
||||||
sym = sym.upper()
|
sym = sym.upper()
|
||||||
sym_info = await client.symbol_info(sym)
|
si: Pair = await client.symbol_info(sym)
|
||||||
try:
|
# try:
|
||||||
si = Pair(**sym_info) # validation
|
# si = Pair(**sym_info) # validation
|
||||||
except TypeError:
|
# except TypeError:
|
||||||
fields_diff = set(sym_info) - set(Pair.__struct_fields__)
|
# fields_diff = set(sym_info) - set(Pair.__struct_fields__)
|
||||||
raise TypeError(
|
# raise TypeError(
|
||||||
f'Missing msg fields {fields_diff}'
|
# f'Missing msg fields {fields_diff}'
|
||||||
)
|
# )
|
||||||
syminfo = si.to_dict()
|
syminfo = si.to_dict()
|
||||||
syminfo['price_tick_size'] = 1 / 10**si.pair_decimals
|
syminfo['price_tick_size'] = 1. / 10**si.pair_decimals
|
||||||
syminfo['lot_tick_size'] = 1 / 10**si.lot_decimals
|
syminfo['lot_tick_size'] = 1. / 10**si.lot_decimals
|
||||||
syminfo['asset_type'] = 'crypto'
|
syminfo['asset_type'] = 'crypto'
|
||||||
sym_infos[sym] = syminfo
|
sym_infos[sym] = syminfo
|
||||||
ws_pairs[sym] = si.wsname
|
ws_pairs[sym] = si.wsname
|
||||||
|
|
|
@ -38,6 +38,7 @@ import tractor
|
||||||
|
|
||||||
from .. import data
|
from .. import data
|
||||||
from ..data.types import Struct
|
from ..data.types import Struct
|
||||||
|
from ..data._source import Symbol
|
||||||
from ..pp import (
|
from ..pp import (
|
||||||
Position,
|
Position,
|
||||||
Transaction,
|
Transaction,
|
||||||
|
@ -81,6 +82,7 @@ class PaperBoi(Struct):
|
||||||
_reqids: bidict
|
_reqids: bidict
|
||||||
_positions: dict[str, Position]
|
_positions: dict[str, Position]
|
||||||
_trade_ledger: dict[str, Any]
|
_trade_ledger: dict[str, Any]
|
||||||
|
_syms: dict[str, Symbol] = {}
|
||||||
|
|
||||||
# init edge case L1 spread
|
# init edge case L1 spread
|
||||||
last_ask: tuple[float, float] = (float('inf'), 0) # price, size
|
last_ask: tuple[float, float] = (float('inf'), 0) # price, size
|
||||||
|
@ -252,6 +254,7 @@ class PaperBoi(Struct):
|
||||||
key = fqsn.rstrip(f'.{self.broker}')
|
key = fqsn.rstrip(f'.{self.broker}')
|
||||||
t = Transaction(
|
t = Transaction(
|
||||||
fqsn=fqsn,
|
fqsn=fqsn,
|
||||||
|
sym=self._syms[fqsn],
|
||||||
tid=oid,
|
tid=oid,
|
||||||
size=size,
|
size=size,
|
||||||
price=price,
|
price=price,
|
||||||
|
@ -261,27 +264,29 @@ class PaperBoi(Struct):
|
||||||
)
|
)
|
||||||
|
|
||||||
with (
|
with (
|
||||||
open_trade_ledger(self.broker, 'paper') as ledger,
|
open_trade_ledger(self.broker, 'paper') as ledger,
|
||||||
open_pps(self.broker, 'paper', True) as table
|
open_pps(self.broker, 'paper', write_on_exit=True) as table
|
||||||
):
|
):
|
||||||
ledger.update({oid: t.to_dict()})
|
tx = t.to_dict()
|
||||||
# Write to pps toml right now
|
tx.pop('sym')
|
||||||
table.update_from_trans({oid: t})
|
ledger.update({oid: tx})
|
||||||
|
# Write to pps toml right now
|
||||||
|
table.update_from_trans({oid: t})
|
||||||
|
|
||||||
pp = table.pps[key]
|
pp = table.pps[key]
|
||||||
pp_msg = BrokerdPosition(
|
pp_msg = BrokerdPosition(
|
||||||
broker=self.broker,
|
broker=self.broker,
|
||||||
account='paper',
|
account='paper',
|
||||||
symbol=fqsn,
|
symbol=fqsn,
|
||||||
# TODO: we need to look up the asset currency from
|
# TODO: we need to look up the asset currency from
|
||||||
# broker info. i guess for crypto this can be
|
# broker info. i guess for crypto this can be
|
||||||
# inferred from the pair?
|
# inferred from the pair?
|
||||||
currency=key,
|
currency=key,
|
||||||
size=pp.size,
|
size=pp.size,
|
||||||
avg_price=pp.ppu,
|
avg_price=pp.ppu,
|
||||||
)
|
)
|
||||||
|
|
||||||
await self.ems_trades_stream.send(pp_msg)
|
await self.ems_trades_stream.send(pp_msg)
|
||||||
|
|
||||||
|
|
||||||
async def simulate_fills(
|
async def simulate_fills(
|
||||||
|
@ -567,6 +572,10 @@ async def trades_dialogue(
|
||||||
|
|
||||||
# TODO: load postions from ledger file
|
# TODO: load postions from ledger file
|
||||||
_trade_ledger={},
|
_trade_ledger={},
|
||||||
|
_syms={
|
||||||
|
fqsn: flume.symbol
|
||||||
|
for fqsn, flume in feed.flumes.items()
|
||||||
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
n.start_soon(
|
n.start_soon(
|
||||||
|
|
|
@ -237,6 +237,7 @@ def write(
|
||||||
config: dict, # toml config as dict
|
config: dict, # toml config as dict
|
||||||
name: str = 'brokers',
|
name: str = 'brokers',
|
||||||
path: str = None,
|
path: str = None,
|
||||||
|
fail_empty: bool = True,
|
||||||
**toml_kwargs,
|
**toml_kwargs,
|
||||||
|
|
||||||
) -> None:
|
) -> None:
|
||||||
|
@ -252,7 +253,7 @@ def write(
|
||||||
log.debug(f"Creating config dir {_config_dir}")
|
log.debug(f"Creating config dir {_config_dir}")
|
||||||
os.makedirs(dirname)
|
os.makedirs(dirname)
|
||||||
|
|
||||||
if not config:
|
if not config and fail_empty:
|
||||||
raise ValueError(
|
raise ValueError(
|
||||||
"Watch out you're trying to write a blank config!")
|
"Watch out you're trying to write a blank config!")
|
||||||
|
|
||||||
|
|
|
@ -18,8 +18,11 @@
|
||||||
numpy data source coversion helpers.
|
numpy data source coversion helpers.
|
||||||
"""
|
"""
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
from decimal import (
|
||||||
|
Decimal,
|
||||||
|
ROUND_HALF_EVEN,
|
||||||
|
)
|
||||||
from typing import Any
|
from typing import Any
|
||||||
import decimal
|
|
||||||
|
|
||||||
from bidict import bidict
|
from bidict import bidict
|
||||||
import numpy as np
|
import numpy as np
|
||||||
|
@ -77,10 +80,14 @@ def mk_fqsn(
|
||||||
def float_digits(
|
def float_digits(
|
||||||
value: float,
|
value: float,
|
||||||
) -> int:
|
) -> int:
|
||||||
|
'''
|
||||||
|
Return the number of precision digits read from a float value.
|
||||||
|
|
||||||
|
'''
|
||||||
if value == 0:
|
if value == 0:
|
||||||
return 0
|
return 0
|
||||||
|
|
||||||
return int(-decimal.Decimal(str(value)).as_tuple().exponent)
|
return int(-Decimal(str(value)).as_tuple().exponent)
|
||||||
|
|
||||||
|
|
||||||
def ohlc_zeros(length: int) -> np.ndarray:
|
def ohlc_zeros(length: int) -> np.ndarray:
|
||||||
|
@ -127,6 +134,56 @@ def unpack_fqsn(fqsn: str) -> tuple[str, str, str]:
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class MktPair(Struct, frozen=True):
|
||||||
|
|
||||||
|
src: str # source asset name being used to buy
|
||||||
|
src_type: str # source asset's financial type/classification name
|
||||||
|
# ^ specifies a "class" of financial instrument
|
||||||
|
# egs. stock, futer, option, bond etc.
|
||||||
|
|
||||||
|
dst: str # destination asset name being bought
|
||||||
|
dst_type: str # destination asset's financial type/classification name
|
||||||
|
|
||||||
|
price_tick: float # minimum price increment value increment
|
||||||
|
price_tick_digits: int # required decimal digits for above
|
||||||
|
|
||||||
|
size_tick: float # minimum size (aka vlm) increment value increment
|
||||||
|
size_tick_digits: int # required decimal digits for above
|
||||||
|
|
||||||
|
venue: str | None = None # market venue provider name
|
||||||
|
expiry: str | None = None # for derivs, expiry datetime parseable str
|
||||||
|
|
||||||
|
# for derivs, info describing contract, egs.
|
||||||
|
# strike price, call or put, swap type, exercise model, etc.
|
||||||
|
contract_info: str | None = None
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def from_msg(
|
||||||
|
self,
|
||||||
|
msg: dict[str, Any],
|
||||||
|
|
||||||
|
) -> MktPair:
|
||||||
|
'''
|
||||||
|
Constructor for a received msg-dict normally received over IPC.
|
||||||
|
|
||||||
|
'''
|
||||||
|
...
|
||||||
|
|
||||||
|
# fqa, fqma, .. etc. see issue:
|
||||||
|
# https://github.com/pikers/piker/issues/467
|
||||||
|
@property
|
||||||
|
def fqsn(self) -> str:
|
||||||
|
'''
|
||||||
|
Return the fully qualified market (endpoint) name for the
|
||||||
|
pair of transacting assets.
|
||||||
|
|
||||||
|
'''
|
||||||
|
...
|
||||||
|
|
||||||
|
|
||||||
|
# TODO: rework the below `Symbol` (which was originally inspired and
|
||||||
|
# derived from stuff in quantdom) into a simpler, ipc msg ready, market
|
||||||
|
# endpoint meta-data container type as per the drafted interace above.
|
||||||
class Symbol(Struct):
|
class Symbol(Struct):
|
||||||
'''
|
'''
|
||||||
I guess this is some kinda container thing for dealing with
|
I guess this is some kinda container thing for dealing with
|
||||||
|
@ -141,10 +198,6 @@ class Symbol(Struct):
|
||||||
suffix: str = ''
|
suffix: str = ''
|
||||||
broker_info: dict[str, dict[str, Any]] = {}
|
broker_info: dict[str, dict[str, Any]] = {}
|
||||||
|
|
||||||
# specifies a "class" of financial instrument
|
|
||||||
# ex. stock, futer, option, bond etc.
|
|
||||||
|
|
||||||
# @validate_arguments
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def from_broker_info(
|
def from_broker_info(
|
||||||
cls,
|
cls,
|
||||||
|
@ -156,14 +209,14 @@ class Symbol(Struct):
|
||||||
) -> Symbol:
|
) -> Symbol:
|
||||||
|
|
||||||
tick_size = info.get('price_tick_size', 0.01)
|
tick_size = info.get('price_tick_size', 0.01)
|
||||||
lot_tick_size = info.get('lot_tick_size', 0.0)
|
lot_size = info.get('lot_tick_size', 0.0)
|
||||||
|
|
||||||
return Symbol(
|
return Symbol(
|
||||||
key=symbol,
|
key=symbol,
|
||||||
tick_size=tick_size,
|
tick_size=tick_size,
|
||||||
lot_tick_size=lot_tick_size,
|
lot_tick_size=lot_size,
|
||||||
tick_size_digits=float_digits(tick_size),
|
tick_size_digits=float_digits(tick_size),
|
||||||
lot_size_digits=float_digits(lot_tick_size),
|
lot_size_digits=float_digits(lot_size),
|
||||||
suffix=suffix,
|
suffix=suffix,
|
||||||
broker_info={broker: info},
|
broker_info={broker: info},
|
||||||
)
|
)
|
||||||
|
@ -244,15 +297,21 @@ class Symbol(Struct):
|
||||||
fqsn = '.'.join(map(str.lower, tokens))
|
fqsn = '.'.join(map(str.lower, tokens))
|
||||||
return fqsn
|
return fqsn
|
||||||
|
|
||||||
def iterfqsns(self) -> list[str]:
|
def quantize_size(
|
||||||
keys = []
|
self,
|
||||||
for broker in self.broker_info.keys():
|
size: float,
|
||||||
fqsn = mk_fqsn(self.key, broker)
|
|
||||||
if self.suffix:
|
|
||||||
fqsn += f'.{self.suffix}'
|
|
||||||
keys.append(fqsn)
|
|
||||||
|
|
||||||
return keys
|
) -> Decimal:
|
||||||
|
'''
|
||||||
|
Truncate input ``size: float`` using ``Decimal``
|
||||||
|
and ``.lot_size_digits``.
|
||||||
|
|
||||||
|
'''
|
||||||
|
digits = self.lot_size_digits
|
||||||
|
return Decimal(size).quantize(
|
||||||
|
Decimal(f'1.{"0".ljust(digits, "0")}'),
|
||||||
|
rounding=ROUND_HALF_EVEN
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
def _nan_to_closest_num(array: np.ndarray):
|
def _nan_to_closest_num(array: np.ndarray):
|
||||||
|
|
70
piker/pp.py
70
piker/pp.py
|
@ -44,7 +44,7 @@ import toml
|
||||||
from . import config
|
from . import config
|
||||||
from .brokers import get_brokermod
|
from .brokers import get_brokermod
|
||||||
from .clearing._messages import BrokerdPosition, Status
|
from .clearing._messages import BrokerdPosition, Status
|
||||||
from .data._source import Symbol
|
from .data._source import Symbol, unpack_fqsn
|
||||||
from .log import get_logger
|
from .log import get_logger
|
||||||
from .data.types import Struct
|
from .data.types import Struct
|
||||||
|
|
||||||
|
@ -82,17 +82,19 @@ def open_trade_ledger(
|
||||||
with open(tradesfile, 'rb') as cf:
|
with open(tradesfile, 'rb') as cf:
|
||||||
start = time.time()
|
start = time.time()
|
||||||
ledger = tomli.load(cf)
|
ledger = tomli.load(cf)
|
||||||
print(f'Ledger load took {time.time() - start}s')
|
log.info(f'Ledger load took {time.time() - start}s')
|
||||||
cpy = ledger.copy()
|
cpy = ledger.copy()
|
||||||
|
|
||||||
try:
|
try:
|
||||||
yield cpy
|
yield cpy
|
||||||
finally:
|
finally:
|
||||||
if cpy != ledger:
|
if cpy != ledger:
|
||||||
|
|
||||||
# TODO: show diff output?
|
# TODO: show diff output?
|
||||||
# https://stackoverflow.com/questions/12956957/print-diff-of-python-dictionaries
|
# https://stackoverflow.com/questions/12956957/print-diff-of-python-dictionaries
|
||||||
print(f'Updating ledger for {tradesfile}:\n')
|
log.info(f'Updating ledger for {tradesfile}:\n')
|
||||||
ledger.update(cpy)
|
ledger.update(cpy)
|
||||||
|
|
||||||
# we write on close the mutated ledger data
|
# we write on close the mutated ledger data
|
||||||
with open(tradesfile, 'w') as cf:
|
with open(tradesfile, 'w') as cf:
|
||||||
toml.dump(ledger, cf)
|
toml.dump(ledger, cf)
|
||||||
|
@ -102,17 +104,18 @@ class Transaction(Struct, frozen=True):
|
||||||
# TODO: should this be ``.to`` (see below)?
|
# TODO: should this be ``.to`` (see below)?
|
||||||
fqsn: str
|
fqsn: str
|
||||||
|
|
||||||
|
sym: Symbol
|
||||||
tid: Union[str, int] # unique transaction id
|
tid: Union[str, int] # unique transaction id
|
||||||
size: float
|
size: float
|
||||||
price: float
|
price: float
|
||||||
cost: float # commisions or other additional costs
|
cost: float # commisions or other additional costs
|
||||||
dt: datetime
|
dt: datetime
|
||||||
expiry: Optional[datetime] = None
|
expiry: datetime | None = None
|
||||||
|
|
||||||
# optional key normally derived from the broker
|
# optional key normally derived from the broker
|
||||||
# backend which ensures the instrument-symbol this record
|
# backend which ensures the instrument-symbol this record
|
||||||
# is for is truly unique.
|
# is for is truly unique.
|
||||||
bsuid: Optional[Union[str, int]] = None
|
bsuid: Union[str, int] | None = None
|
||||||
|
|
||||||
# optional fqsn for the source "asset"/money symbol?
|
# optional fqsn for the source "asset"/money symbol?
|
||||||
# from: Optional[str] = None
|
# from: Optional[str] = None
|
||||||
|
@ -192,6 +195,13 @@ class Position(Struct):
|
||||||
s = d.pop('symbol')
|
s = d.pop('symbol')
|
||||||
fqsn = s.front_fqsn()
|
fqsn = s.front_fqsn()
|
||||||
|
|
||||||
|
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['price_tick_size']
|
||||||
|
d['lot_tick_size'] = sym_info['lot_tick_size']
|
||||||
|
|
||||||
if self.expiry is None:
|
if self.expiry is None:
|
||||||
d.pop('expiry', None)
|
d.pop('expiry', None)
|
||||||
elif expiry:
|
elif expiry:
|
||||||
|
@ -466,7 +476,7 @@ class Position(Struct):
|
||||||
if self.split_ratio is not None:
|
if self.split_ratio is not None:
|
||||||
size = round(size * self.split_ratio)
|
size = round(size * self.split_ratio)
|
||||||
|
|
||||||
return size
|
return float(self.symbol.quantize_size(size))
|
||||||
|
|
||||||
def minimize_clears(
|
def minimize_clears(
|
||||||
self,
|
self,
|
||||||
|
@ -510,7 +520,7 @@ class Position(Struct):
|
||||||
'cost': t.cost,
|
'cost': t.cost,
|
||||||
'price': t.price,
|
'price': t.price,
|
||||||
'size': t.size,
|
'size': t.size,
|
||||||
'dt': t.dt,
|
'dt': t.dt
|
||||||
}
|
}
|
||||||
|
|
||||||
# TODO: compute these incrementally instead
|
# TODO: compute these incrementally instead
|
||||||
|
@ -557,7 +567,7 @@ class PpTable(Struct):
|
||||||
Symbol.from_fqsn(
|
Symbol.from_fqsn(
|
||||||
t.fqsn,
|
t.fqsn,
|
||||||
info={},
|
info={},
|
||||||
),
|
) if not t.sym else t.sym,
|
||||||
size=0.0,
|
size=0.0,
|
||||||
ppu=0.0,
|
ppu=0.0,
|
||||||
bsuid=t.bsuid,
|
bsuid=t.bsuid,
|
||||||
|
@ -680,11 +690,20 @@ class PpTable(Struct):
|
||||||
'''
|
'''
|
||||||
# TODO: show diff output?
|
# TODO: show diff output?
|
||||||
# https://stackoverflow.com/questions/12956957/print-diff-of-python-dictionaries
|
# https://stackoverflow.com/questions/12956957/print-diff-of-python-dictionaries
|
||||||
print(f'Updating ``pps.toml`` for {path}:\n')
|
|
||||||
|
|
||||||
# active, closed_pp_objs = table.dump_active()
|
# active, closed_pp_objs = table.dump_active()
|
||||||
pp_entries = self.to_toml()
|
pp_entries = self.to_toml()
|
||||||
self.conf[self.brokername][self.acctid] = pp_entries
|
if pp_entries:
|
||||||
|
log.info(f'Updating ``pps.toml`` for {path}:\n')
|
||||||
|
log.info(f'Current positions:\n{pp_entries}')
|
||||||
|
self.conf[self.brokername][self.acctid] = pp_entries
|
||||||
|
|
||||||
|
elif (
|
||||||
|
self.brokername in self.conf and
|
||||||
|
self.acctid in self.conf[self.brokername]
|
||||||
|
):
|
||||||
|
del self.conf[self.brokername][self.acctid]
|
||||||
|
if len(self.conf[self.brokername]) == 0:
|
||||||
|
del self.conf[self.brokername]
|
||||||
|
|
||||||
# TODO: why tf haven't they already done this for inline
|
# TODO: why tf haven't they already done this for inline
|
||||||
# tables smh..
|
# tables smh..
|
||||||
|
@ -698,6 +717,7 @@ class PpTable(Struct):
|
||||||
self.conf,
|
self.conf,
|
||||||
'pps',
|
'pps',
|
||||||
encoder=enc,
|
encoder=enc,
|
||||||
|
fail_empty=False
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@ -881,7 +901,6 @@ def open_pps(
|
||||||
brokername: str,
|
brokername: str,
|
||||||
acctid: str,
|
acctid: str,
|
||||||
write_on_exit: bool = False,
|
write_on_exit: bool = False,
|
||||||
|
|
||||||
) -> Generator[PpTable, None, None]:
|
) -> Generator[PpTable, None, None]:
|
||||||
'''
|
'''
|
||||||
Read out broker-specific position entries from
|
Read out broker-specific position entries from
|
||||||
|
@ -914,6 +933,21 @@ def open_pps(
|
||||||
# and update `PpTable` obj entries.
|
# and update `PpTable` obj entries.
|
||||||
for fqsn, entry in pps.items():
|
for fqsn, entry in pps.items():
|
||||||
bsuid = entry['bsuid']
|
bsuid = entry['bsuid']
|
||||||
|
symbol = Symbol.from_fqsn(
|
||||||
|
fqsn,
|
||||||
|
|
||||||
|
# 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),
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
# convert clears sub-tables (only in this form
|
# convert clears sub-tables (only in this form
|
||||||
# for toml re-presentation) back into a master table.
|
# for toml re-presentation) back into a master table.
|
||||||
|
@ -935,8 +969,10 @@ def open_pps(
|
||||||
dtstr = clears_table['dt']
|
dtstr = clears_table['dt']
|
||||||
dt = pendulum.parse(dtstr)
|
dt = pendulum.parse(dtstr)
|
||||||
clears_table['dt'] = dt
|
clears_table['dt'] = dt
|
||||||
|
|
||||||
trans.append(Transaction(
|
trans.append(Transaction(
|
||||||
fqsn=bsuid,
|
fqsn=bsuid,
|
||||||
|
sym=symbol,
|
||||||
bsuid=bsuid,
|
bsuid=bsuid,
|
||||||
tid=tid,
|
tid=tid,
|
||||||
size=clears_table['size'],
|
size=clears_table['size'],
|
||||||
|
@ -949,7 +985,11 @@ 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')
|
split_ratio = entry.get('split_ratio')
|
||||||
|
|
||||||
expiry = entry.get('expiry')
|
expiry = entry.get('expiry')
|
||||||
|
@ -957,7 +997,7 @@ def open_pps(
|
||||||
expiry = pendulum.parse(expiry)
|
expiry = pendulum.parse(expiry)
|
||||||
|
|
||||||
pp = pp_objs[bsuid] = Position(
|
pp = pp_objs[bsuid] = Position(
|
||||||
Symbol.from_fqsn(fqsn, info={}),
|
symbol,
|
||||||
size=size,
|
size=size,
|
||||||
ppu=ppu,
|
ppu=ppu,
|
||||||
split_ratio=split_ratio,
|
split_ratio=split_ratio,
|
||||||
|
|
|
@ -84,25 +84,20 @@ async def _async_main(
|
||||||
case {'name': 'position'}:
|
case {'name': 'position'}:
|
||||||
break
|
break
|
||||||
|
|
||||||
if (
|
|
||||||
assert_entries
|
|
||||||
or assert_pps
|
|
||||||
or assert_zeroed_pps
|
|
||||||
or assert_msg
|
|
||||||
):
|
|
||||||
_assert(
|
|
||||||
assert_entries,
|
|
||||||
assert_pps,
|
|
||||||
assert_zeroed_pps,
|
|
||||||
pps,
|
|
||||||
last_msg,
|
|
||||||
size,
|
|
||||||
executions,
|
|
||||||
)
|
|
||||||
|
|
||||||
# Teardown piker like a user would
|
# Teardown piker like a user would
|
||||||
raise KeyboardInterrupt
|
raise KeyboardInterrupt
|
||||||
|
|
||||||
|
if assert_entries or assert_pps or assert_zeroed_pps or assert_msg:
|
||||||
|
_assert(
|
||||||
|
assert_entries,
|
||||||
|
assert_pps,
|
||||||
|
assert_zeroed_pps,
|
||||||
|
pps,
|
||||||
|
last_msg,
|
||||||
|
size,
|
||||||
|
executions,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
def _assert(
|
def _assert(
|
||||||
assert_entries,
|
assert_entries,
|
||||||
|
@ -206,8 +201,6 @@ def test_sell(
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.xfail(reason='Due to precision issues, this test will currently fail')
|
|
||||||
def test_multi_sell(
|
def test_multi_sell(
|
||||||
open_test_pikerd_and_ems: AsyncContextManager,
|
open_test_pikerd_and_ems: AsyncContextManager,
|
||||||
delete_testing_dir
|
delete_testing_dir
|
||||||
|
|
Loading…
Reference in New Issue