piker/piker/accounting/_ledger.py

422 lines
12 KiB
Python
Raw Normal View History

# piker: trading gear for hackers
# 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
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Affero General Public License for more details.
# You should have received a copy of the GNU Affero General Public License
# along with this program. If not, see <https://www.gnu.org/licenses/>.
'''
Trade and transaction ledger processing.
'''
from __future__ import annotations
from collections import UserDict
from contextlib import contextmanager as cm
from functools import partial
from pathlib import Path
from pprint import pformat
from types import ModuleType
from typing import (
Any,
Callable,
Generator,
Literal,
TYPE_CHECKING,
)
from pendulum import (
Formalize transaction normalizer func signature Since each broker backend generally needs to define a specific field-name-schema to determine the exact instantiation arguments to `Transaction`, we generally need each backend to define an endpoint function to conduct this transaction from an input `dict[str, Any]` received either directly from provided ledger APIs or from previously stored `.accounting._ledger` saved trades ledger TOML files. To accomplish this we now require backends to declare a new routine: ```python def norm_trade( tid: str, # the uuid for the transaction txdict: dict, # the input record-dict # a table of mkt-symbols to backend # struct objects which define the (meta-data) for the backend specific # venue's symbology pairs: dict[str, Struct], ) -> Transaction: ... ``` which implements that record conversion (at least for trades) and can thus be used in `TransactionLedger.iter_txns()` which requires "some code" to implement the loading from a serialization format (aka the input `dict` record) to our local `Transaction` struct, normally also using a `Pair`-struct table defined (and maybe previously cached) by the specific backend such our (normalization layer's) `MktPair`'s fields can be set. For the case of our `.clearing._paper_engine` we def the routine to simply extract the exact same fields from the TOML ledger records that we previously had written (to it) and define it in that module. Also, we always pass `pairs=SymbologyCache.pairs: dict[str, Struct]` on norm trade calls such that offline ledger and accounting processing clients can use a previously cached symbology set without having to necessarily start the async-actor runtime to query the actual backend API if the data has already been saved locally on the system B) Other related: - always passthrough kwargs in overridden `.to_dict()` method. - only do fqme related trade record field name rewrites/names when operating on a paper ledger; normally a backend's records don't contain these. - fix `pendulum.DateTime` type annots. - just deliver `Transaction`s from `.iter_txns()`
2023-07-14 18:11:49 +00:00
DateTime,
)
import tomli_w # for fast ledger writing
from piker.types import Struct
from piker import config
from ..log import get_logger
Rework `.accounting.Position` calcs to prep for `polars` We're probably going to move to implementing all accounting using `polars.DataFrame` and friends and thus this rejig preps for a much more "stateless" implementation of our `Position` type and its internal pos-accounting metrics: `ppu` and `cumsize`. Summary: - wrt to `._pos.Position`: - rename `.size`/`.accum_size` to `.cumsize` to be more in line with `polars.DataFrame.cumsum()`. - make `Position.expiry` delegate to the underlying `.mkt: MktPair` handling (hopefully) all edge cases.. - change over to a new `._events: dict[str, Transaction]` in prep for #510 (and friends) and enforce a new `Transaction.etype: str` which is by default `clear`. - add `.iter_by_type()` which iterates, filters and sorts the entries in `._events` from above. - add `Position.clearsdict()` which returns the dict-ified and datetime-sorted table which can more-or-less be stored in the toml account file. - add `.minimized_clears()` a new (and close) version of the old method which always grabs at least one clear before a position-side-polarity-change. - mask-drop `.ensure_state()` since there is no more `.size`/`.price` state vars (per say) as we always re-calc the ppu and cumsize from the clears records on every read. - `.add_clear` no longer does bisec insorting since all sorting is done on position properties *reads*. - move the PPU (price per unit) calculator to a new `.accounting.calcs` as well as add in the `iter_by_dt()` clearing transaction sorted iterator. - also make some fixes to this to handle both lists of `Transaction` as well as `dict`s as before. - start rename of `PpTable` -> `Account` and make a note about adding a `.balances` table. - always `float()` the transaction size/price values since it seems if they get processed as `tomlkit.Integer` there's some suuper weird double negative on read-then-write to the clears table? - something like `cumsize = -1` -> `cumsize = --1` !?!? - make `load_pps_from_ledger()` work again but now includes some very very first draft `polars` df processing from a transaction ledger. - use this from the `accounting.cli.disect` subcmd which is also in *super early draft* mode ;) - obviously as mentioned in the `Position` section, add the new `.calcs` module with a `.ppu()` calculator func B)
2023-07-03 22:52:02 +00:00
from .calc import (
iter_by_dt,
)
if TYPE_CHECKING:
from ..data._symcache import (
SymbologyCache,
)
log = get_logger(__name__)
TxnType = Literal[
'clear',
'transfer',
# TODO: see https://github.com/pikers/piker/issues/510
# 'split',
# 'rename',
# 'resize',
# 'removal',
]
class Transaction(Struct, frozen=True):
# NOTE: this is a unified acronym also used in our `MktPair`
# and can stand for any of a
# "fully qualified <blank> endpoint":
# - "market" in the case of financial trades
# (btcusdt.spot.binance).
# - "merkel (tree)" aka a blockchain system "wallet tranfers"
# (btc.blockchain)
# - "money" for tradtitional (digital databases)
# *bank accounts* (usd.swift, eur.sepa)
fqme: str
Rework `.accounting.Position` calcs to prep for `polars` We're probably going to move to implementing all accounting using `polars.DataFrame` and friends and thus this rejig preps for a much more "stateless" implementation of our `Position` type and its internal pos-accounting metrics: `ppu` and `cumsize`. Summary: - wrt to `._pos.Position`: - rename `.size`/`.accum_size` to `.cumsize` to be more in line with `polars.DataFrame.cumsum()`. - make `Position.expiry` delegate to the underlying `.mkt: MktPair` handling (hopefully) all edge cases.. - change over to a new `._events: dict[str, Transaction]` in prep for #510 (and friends) and enforce a new `Transaction.etype: str` which is by default `clear`. - add `.iter_by_type()` which iterates, filters and sorts the entries in `._events` from above. - add `Position.clearsdict()` which returns the dict-ified and datetime-sorted table which can more-or-less be stored in the toml account file. - add `.minimized_clears()` a new (and close) version of the old method which always grabs at least one clear before a position-side-polarity-change. - mask-drop `.ensure_state()` since there is no more `.size`/`.price` state vars (per say) as we always re-calc the ppu and cumsize from the clears records on every read. - `.add_clear` no longer does bisec insorting since all sorting is done on position properties *reads*. - move the PPU (price per unit) calculator to a new `.accounting.calcs` as well as add in the `iter_by_dt()` clearing transaction sorted iterator. - also make some fixes to this to handle both lists of `Transaction` as well as `dict`s as before. - start rename of `PpTable` -> `Account` and make a note about adding a `.balances` table. - always `float()` the transaction size/price values since it seems if they get processed as `tomlkit.Integer` there's some suuper weird double negative on read-then-write to the clears table? - something like `cumsize = -1` -> `cumsize = --1` !?!? - make `load_pps_from_ledger()` work again but now includes some very very first draft `polars` df processing from a transaction ledger. - use this from the `accounting.cli.disect` subcmd which is also in *super early draft* mode ;) - obviously as mentioned in the `Position` section, add the new `.calcs` module with a `.ppu()` calculator func B)
2023-07-03 22:52:02 +00:00
tid: str | int # unique transaction id
size: float
price: float
cost: float # commisions or other additional costs
Formalize transaction normalizer func signature Since each broker backend generally needs to define a specific field-name-schema to determine the exact instantiation arguments to `Transaction`, we generally need each backend to define an endpoint function to conduct this transaction from an input `dict[str, Any]` received either directly from provided ledger APIs or from previously stored `.accounting._ledger` saved trades ledger TOML files. To accomplish this we now require backends to declare a new routine: ```python def norm_trade( tid: str, # the uuid for the transaction txdict: dict, # the input record-dict # a table of mkt-symbols to backend # struct objects which define the (meta-data) for the backend specific # venue's symbology pairs: dict[str, Struct], ) -> Transaction: ... ``` which implements that record conversion (at least for trades) and can thus be used in `TransactionLedger.iter_txns()` which requires "some code" to implement the loading from a serialization format (aka the input `dict` record) to our local `Transaction` struct, normally also using a `Pair`-struct table defined (and maybe previously cached) by the specific backend such our (normalization layer's) `MktPair`'s fields can be set. For the case of our `.clearing._paper_engine` we def the routine to simply extract the exact same fields from the TOML ledger records that we previously had written (to it) and define it in that module. Also, we always pass `pairs=SymbologyCache.pairs: dict[str, Struct]` on norm trade calls such that offline ledger and accounting processing clients can use a previously cached symbology set without having to necessarily start the async-actor runtime to query the actual backend API if the data has already been saved locally on the system B) Other related: - always passthrough kwargs in overridden `.to_dict()` method. - only do fqme related trade record field name rewrites/names when operating on a paper ledger; normally a backend's records don't contain these. - fix `pendulum.DateTime` type annots. - just deliver `Transaction`s from `.iter_txns()`
2023-07-14 18:11:49 +00:00
dt: DateTime
# the "event type" in terms of "market events" see above and
# https://github.com/pikers/piker/issues/510
etype: TxnType = 'clear'
Rework `.accounting.Position` calcs to prep for `polars` We're probably going to move to implementing all accounting using `polars.DataFrame` and friends and thus this rejig preps for a much more "stateless" implementation of our `Position` type and its internal pos-accounting metrics: `ppu` and `cumsize`. Summary: - wrt to `._pos.Position`: - rename `.size`/`.accum_size` to `.cumsize` to be more in line with `polars.DataFrame.cumsum()`. - make `Position.expiry` delegate to the underlying `.mkt: MktPair` handling (hopefully) all edge cases.. - change over to a new `._events: dict[str, Transaction]` in prep for #510 (and friends) and enforce a new `Transaction.etype: str` which is by default `clear`. - add `.iter_by_type()` which iterates, filters and sorts the entries in `._events` from above. - add `Position.clearsdict()` which returns the dict-ified and datetime-sorted table which can more-or-less be stored in the toml account file. - add `.minimized_clears()` a new (and close) version of the old method which always grabs at least one clear before a position-side-polarity-change. - mask-drop `.ensure_state()` since there is no more `.size`/`.price` state vars (per say) as we always re-calc the ppu and cumsize from the clears records on every read. - `.add_clear` no longer does bisec insorting since all sorting is done on position properties *reads*. - move the PPU (price per unit) calculator to a new `.accounting.calcs` as well as add in the `iter_by_dt()` clearing transaction sorted iterator. - also make some fixes to this to handle both lists of `Transaction` as well as `dict`s as before. - start rename of `PpTable` -> `Account` and make a note about adding a `.balances` table. - always `float()` the transaction size/price values since it seems if they get processed as `tomlkit.Integer` there's some suuper weird double negative on read-then-write to the clears table? - something like `cumsize = -1` -> `cumsize = --1` !?!? - make `load_pps_from_ledger()` work again but now includes some very very first draft `polars` df processing from a transaction ledger. - use this from the `accounting.cli.disect` subcmd which is also in *super early draft* mode ;) - obviously as mentioned in the `Position` section, add the new `.calcs` module with a `.ppu()` calculator func B)
2023-07-03 22:52:02 +00:00
# TODO: we can drop this right since we
# can instead expect the backend to provide this
# via the `MktPair`?
Formalize transaction normalizer func signature Since each broker backend generally needs to define a specific field-name-schema to determine the exact instantiation arguments to `Transaction`, we generally need each backend to define an endpoint function to conduct this transaction from an input `dict[str, Any]` received either directly from provided ledger APIs or from previously stored `.accounting._ledger` saved trades ledger TOML files. To accomplish this we now require backends to declare a new routine: ```python def norm_trade( tid: str, # the uuid for the transaction txdict: dict, # the input record-dict # a table of mkt-symbols to backend # struct objects which define the (meta-data) for the backend specific # venue's symbology pairs: dict[str, Struct], ) -> Transaction: ... ``` which implements that record conversion (at least for trades) and can thus be used in `TransactionLedger.iter_txns()` which requires "some code" to implement the loading from a serialization format (aka the input `dict` record) to our local `Transaction` struct, normally also using a `Pair`-struct table defined (and maybe previously cached) by the specific backend such our (normalization layer's) `MktPair`'s fields can be set. For the case of our `.clearing._paper_engine` we def the routine to simply extract the exact same fields from the TOML ledger records that we previously had written (to it) and define it in that module. Also, we always pass `pairs=SymbologyCache.pairs: dict[str, Struct]` on norm trade calls such that offline ledger and accounting processing clients can use a previously cached symbology set without having to necessarily start the async-actor runtime to query the actual backend API if the data has already been saved locally on the system B) Other related: - always passthrough kwargs in overridden `.to_dict()` method. - only do fqme related trade record field name rewrites/names when operating on a paper ledger; normally a backend's records don't contain these. - fix `pendulum.DateTime` type annots. - just deliver `Transaction`s from `.iter_txns()`
2023-07-14 18:11:49 +00:00
expiry: DateTime | None = None
# (optional) key-id defined by the broker-service backend which
# ensures the instrument-symbol market key for this record is unique
# in the "their backend/system" sense; i.e. this uid for the market
# as defined (internally) in some namespace defined by the broker
# service.
bs_mktid: str | int | None = None
Formalize transaction normalizer func signature Since each broker backend generally needs to define a specific field-name-schema to determine the exact instantiation arguments to `Transaction`, we generally need each backend to define an endpoint function to conduct this transaction from an input `dict[str, Any]` received either directly from provided ledger APIs or from previously stored `.accounting._ledger` saved trades ledger TOML files. To accomplish this we now require backends to declare a new routine: ```python def norm_trade( tid: str, # the uuid for the transaction txdict: dict, # the input record-dict # a table of mkt-symbols to backend # struct objects which define the (meta-data) for the backend specific # venue's symbology pairs: dict[str, Struct], ) -> Transaction: ... ``` which implements that record conversion (at least for trades) and can thus be used in `TransactionLedger.iter_txns()` which requires "some code" to implement the loading from a serialization format (aka the input `dict` record) to our local `Transaction` struct, normally also using a `Pair`-struct table defined (and maybe previously cached) by the specific backend such our (normalization layer's) `MktPair`'s fields can be set. For the case of our `.clearing._paper_engine` we def the routine to simply extract the exact same fields from the TOML ledger records that we previously had written (to it) and define it in that module. Also, we always pass `pairs=SymbologyCache.pairs: dict[str, Struct]` on norm trade calls such that offline ledger and accounting processing clients can use a previously cached symbology set without having to necessarily start the async-actor runtime to query the actual backend API if the data has already been saved locally on the system B) Other related: - always passthrough kwargs in overridden `.to_dict()` method. - only do fqme related trade record field name rewrites/names when operating on a paper ledger; normally a backend's records don't contain these. - fix `pendulum.DateTime` type annots. - just deliver `Transaction`s from `.iter_txns()`
2023-07-14 18:11:49 +00:00
def to_dict(
self,
**kwargs,
) -> dict:
dct: dict[str, Any] = super().to_dict(**kwargs)
# ensure we use a pendulum formatted
# ISO style str here!@
dct['dt'] = str(self.dt)
return dct
class TransactionLedger(UserDict):
'''
Very simple ``dict`` wrapper + ``pathlib.Path`` handle to
a TOML formatted transaction file for enabling file writes
dynamically whilst still looking exactly like a ``dict`` from the
outside.
'''
Formalize transaction normalizer func signature Since each broker backend generally needs to define a specific field-name-schema to determine the exact instantiation arguments to `Transaction`, we generally need each backend to define an endpoint function to conduct this transaction from an input `dict[str, Any]` received either directly from provided ledger APIs or from previously stored `.accounting._ledger` saved trades ledger TOML files. To accomplish this we now require backends to declare a new routine: ```python def norm_trade( tid: str, # the uuid for the transaction txdict: dict, # the input record-dict # a table of mkt-symbols to backend # struct objects which define the (meta-data) for the backend specific # venue's symbology pairs: dict[str, Struct], ) -> Transaction: ... ``` which implements that record conversion (at least for trades) and can thus be used in `TransactionLedger.iter_txns()` which requires "some code" to implement the loading from a serialization format (aka the input `dict` record) to our local `Transaction` struct, normally also using a `Pair`-struct table defined (and maybe previously cached) by the specific backend such our (normalization layer's) `MktPair`'s fields can be set. For the case of our `.clearing._paper_engine` we def the routine to simply extract the exact same fields from the TOML ledger records that we previously had written (to it) and define it in that module. Also, we always pass `pairs=SymbologyCache.pairs: dict[str, Struct]` on norm trade calls such that offline ledger and accounting processing clients can use a previously cached symbology set without having to necessarily start the async-actor runtime to query the actual backend API if the data has already been saved locally on the system B) Other related: - always passthrough kwargs in overridden `.to_dict()` method. - only do fqme related trade record field name rewrites/names when operating on a paper ledger; normally a backend's records don't contain these. - fix `pendulum.DateTime` type annots. - just deliver `Transaction`s from `.iter_txns()`
2023-07-14 18:11:49 +00:00
# NOTE: see `open_trade_ledger()` for defaults, this should
# never be constructed manually!
def __init__(
self,
ledger_dict: dict,
file_path: Path,
account: str,
mod: ModuleType, # broker mod
tx_sort: Callable,
symcache: SymbologyCache,
) -> None:
self.account: str = account
self.file_path: Path = file_path
self.mod: ModuleType = mod
self.tx_sort: Callable = tx_sort
self._symcache: SymbologyCache = symcache
# any added txns we keep in that form for meta-data
# gathering purposes
self._txns: dict[str, Transaction] = {}
super().__init__(ledger_dict)
def __repr__(self) -> str:
return (
f'TransactionLedger: {len(self)}\n'
f'{pformat(list(self.data))}'
)
@property
def symcache(self) -> SymbologyCache:
Formalize transaction normalizer func signature Since each broker backend generally needs to define a specific field-name-schema to determine the exact instantiation arguments to `Transaction`, we generally need each backend to define an endpoint function to conduct this transaction from an input `dict[str, Any]` received either directly from provided ledger APIs or from previously stored `.accounting._ledger` saved trades ledger TOML files. To accomplish this we now require backends to declare a new routine: ```python def norm_trade( tid: str, # the uuid for the transaction txdict: dict, # the input record-dict # a table of mkt-symbols to backend # struct objects which define the (meta-data) for the backend specific # venue's symbology pairs: dict[str, Struct], ) -> Transaction: ... ``` which implements that record conversion (at least for trades) and can thus be used in `TransactionLedger.iter_txns()` which requires "some code" to implement the loading from a serialization format (aka the input `dict` record) to our local `Transaction` struct, normally also using a `Pair`-struct table defined (and maybe previously cached) by the specific backend such our (normalization layer's) `MktPair`'s fields can be set. For the case of our `.clearing._paper_engine` we def the routine to simply extract the exact same fields from the TOML ledger records that we previously had written (to it) and define it in that module. Also, we always pass `pairs=SymbologyCache.pairs: dict[str, Struct]` on norm trade calls such that offline ledger and accounting processing clients can use a previously cached symbology set without having to necessarily start the async-actor runtime to query the actual backend API if the data has already been saved locally on the system B) Other related: - always passthrough kwargs in overridden `.to_dict()` method. - only do fqme related trade record field name rewrites/names when operating on a paper ledger; normally a backend's records don't contain these. - fix `pendulum.DateTime` type annots. - just deliver `Transaction`s from `.iter_txns()`
2023-07-14 18:11:49 +00:00
'''
Read-only ref to backend's ``SymbologyCache``.
'''
return self._symcache
def update_from_t(
self,
t: Transaction,
) -> None:
'''
Given an input `Transaction`, cast to `dict` and update
from it's transaction id.
'''
self.data[t.tid] = t.to_dict()
self._txns[t.tid] = t
def iter_txns(
self,
symcache: SymbologyCache | None = None,
) -> Generator[
Formalize transaction normalizer func signature Since each broker backend generally needs to define a specific field-name-schema to determine the exact instantiation arguments to `Transaction`, we generally need each backend to define an endpoint function to conduct this transaction from an input `dict[str, Any]` received either directly from provided ledger APIs or from previously stored `.accounting._ledger` saved trades ledger TOML files. To accomplish this we now require backends to declare a new routine: ```python def norm_trade( tid: str, # the uuid for the transaction txdict: dict, # the input record-dict # a table of mkt-symbols to backend # struct objects which define the (meta-data) for the backend specific # venue's symbology pairs: dict[str, Struct], ) -> Transaction: ... ``` which implements that record conversion (at least for trades) and can thus be used in `TransactionLedger.iter_txns()` which requires "some code" to implement the loading from a serialization format (aka the input `dict` record) to our local `Transaction` struct, normally also using a `Pair`-struct table defined (and maybe previously cached) by the specific backend such our (normalization layer's) `MktPair`'s fields can be set. For the case of our `.clearing._paper_engine` we def the routine to simply extract the exact same fields from the TOML ledger records that we previously had written (to it) and define it in that module. Also, we always pass `pairs=SymbologyCache.pairs: dict[str, Struct]` on norm trade calls such that offline ledger and accounting processing clients can use a previously cached symbology set without having to necessarily start the async-actor runtime to query the actual backend API if the data has already been saved locally on the system B) Other related: - always passthrough kwargs in overridden `.to_dict()` method. - only do fqme related trade record field name rewrites/names when operating on a paper ledger; normally a backend's records don't contain these. - fix `pendulum.DateTime` type annots. - just deliver `Transaction`s from `.iter_txns()`
2023-07-14 18:11:49 +00:00
Transaction,
None,
None,
]:
'''
Deliver trades records in ``(key: str, t: Transaction)``
form via generator.
'''
symcache = symcache or self._symcache
if self.account == 'paper':
from piker.clearing import _paper_engine
norm_trade: Callable = partial(
_paper_engine.norm_trade,
brokermod=self.mod,
)
else:
norm_trade: Callable = self.mod.norm_trade
Rework `.accounting.Position` calcs to prep for `polars` We're probably going to move to implementing all accounting using `polars.DataFrame` and friends and thus this rejig preps for a much more "stateless" implementation of our `Position` type and its internal pos-accounting metrics: `ppu` and `cumsize`. Summary: - wrt to `._pos.Position`: - rename `.size`/`.accum_size` to `.cumsize` to be more in line with `polars.DataFrame.cumsum()`. - make `Position.expiry` delegate to the underlying `.mkt: MktPair` handling (hopefully) all edge cases.. - change over to a new `._events: dict[str, Transaction]` in prep for #510 (and friends) and enforce a new `Transaction.etype: str` which is by default `clear`. - add `.iter_by_type()` which iterates, filters and sorts the entries in `._events` from above. - add `Position.clearsdict()` which returns the dict-ified and datetime-sorted table which can more-or-less be stored in the toml account file. - add `.minimized_clears()` a new (and close) version of the old method which always grabs at least one clear before a position-side-polarity-change. - mask-drop `.ensure_state()` since there is no more `.size`/`.price` state vars (per say) as we always re-calc the ppu and cumsize from the clears records on every read. - `.add_clear` no longer does bisec insorting since all sorting is done on position properties *reads*. - move the PPU (price per unit) calculator to a new `.accounting.calcs` as well as add in the `iter_by_dt()` clearing transaction sorted iterator. - also make some fixes to this to handle both lists of `Transaction` as well as `dict`s as before. - start rename of `PpTable` -> `Account` and make a note about adding a `.balances` table. - always `float()` the transaction size/price values since it seems if they get processed as `tomlkit.Integer` there's some suuper weird double negative on read-then-write to the clears table? - something like `cumsize = -1` -> `cumsize = --1` !?!? - make `load_pps_from_ledger()` work again but now includes some very very first draft `polars` df processing from a transaction ledger. - use this from the `accounting.cli.disect` subcmd which is also in *super early draft* mode ;) - obviously as mentioned in the `Position` section, add the new `.calcs` module with a `.ppu()` calculator func B)
2023-07-03 22:52:02 +00:00
# datetime-sort and pack into txs
Formalize transaction normalizer func signature Since each broker backend generally needs to define a specific field-name-schema to determine the exact instantiation arguments to `Transaction`, we generally need each backend to define an endpoint function to conduct this transaction from an input `dict[str, Any]` received either directly from provided ledger APIs or from previously stored `.accounting._ledger` saved trades ledger TOML files. To accomplish this we now require backends to declare a new routine: ```python def norm_trade( tid: str, # the uuid for the transaction txdict: dict, # the input record-dict # a table of mkt-symbols to backend # struct objects which define the (meta-data) for the backend specific # venue's symbology pairs: dict[str, Struct], ) -> Transaction: ... ``` which implements that record conversion (at least for trades) and can thus be used in `TransactionLedger.iter_txns()` which requires "some code" to implement the loading from a serialization format (aka the input `dict` record) to our local `Transaction` struct, normally also using a `Pair`-struct table defined (and maybe previously cached) by the specific backend such our (normalization layer's) `MktPair`'s fields can be set. For the case of our `.clearing._paper_engine` we def the routine to simply extract the exact same fields from the TOML ledger records that we previously had written (to it) and define it in that module. Also, we always pass `pairs=SymbologyCache.pairs: dict[str, Struct]` on norm trade calls such that offline ledger and accounting processing clients can use a previously cached symbology set without having to necessarily start the async-actor runtime to query the actual backend API if the data has already been saved locally on the system B) Other related: - always passthrough kwargs in overridden `.to_dict()` method. - only do fqme related trade record field name rewrites/names when operating on a paper ledger; normally a backend's records don't contain these. - fix `pendulum.DateTime` type annots. - just deliver `Transaction`s from `.iter_txns()`
2023-07-14 18:11:49 +00:00
for tid, txdict in self.tx_sort(self.data.items()):
txn: Transaction = norm_trade(
tid,
txdict,
pairs=symcache.pairs,
symcache=symcache,
Formalize transaction normalizer func signature Since each broker backend generally needs to define a specific field-name-schema to determine the exact instantiation arguments to `Transaction`, we generally need each backend to define an endpoint function to conduct this transaction from an input `dict[str, Any]` received either directly from provided ledger APIs or from previously stored `.accounting._ledger` saved trades ledger TOML files. To accomplish this we now require backends to declare a new routine: ```python def norm_trade( tid: str, # the uuid for the transaction txdict: dict, # the input record-dict # a table of mkt-symbols to backend # struct objects which define the (meta-data) for the backend specific # venue's symbology pairs: dict[str, Struct], ) -> Transaction: ... ``` which implements that record conversion (at least for trades) and can thus be used in `TransactionLedger.iter_txns()` which requires "some code" to implement the loading from a serialization format (aka the input `dict` record) to our local `Transaction` struct, normally also using a `Pair`-struct table defined (and maybe previously cached) by the specific backend such our (normalization layer's) `MktPair`'s fields can be set. For the case of our `.clearing._paper_engine` we def the routine to simply extract the exact same fields from the TOML ledger records that we previously had written (to it) and define it in that module. Also, we always pass `pairs=SymbologyCache.pairs: dict[str, Struct]` on norm trade calls such that offline ledger and accounting processing clients can use a previously cached symbology set without having to necessarily start the async-actor runtime to query the actual backend API if the data has already been saved locally on the system B) Other related: - always passthrough kwargs in overridden `.to_dict()` method. - only do fqme related trade record field name rewrites/names when operating on a paper ledger; normally a backend's records don't contain these. - fix `pendulum.DateTime` type annots. - just deliver `Transaction`s from `.iter_txns()`
2023-07-14 18:11:49 +00:00
)
yield txn
Rework `.accounting.Position` calcs to prep for `polars` We're probably going to move to implementing all accounting using `polars.DataFrame` and friends and thus this rejig preps for a much more "stateless" implementation of our `Position` type and its internal pos-accounting metrics: `ppu` and `cumsize`. Summary: - wrt to `._pos.Position`: - rename `.size`/`.accum_size` to `.cumsize` to be more in line with `polars.DataFrame.cumsum()`. - make `Position.expiry` delegate to the underlying `.mkt: MktPair` handling (hopefully) all edge cases.. - change over to a new `._events: dict[str, Transaction]` in prep for #510 (and friends) and enforce a new `Transaction.etype: str` which is by default `clear`. - add `.iter_by_type()` which iterates, filters and sorts the entries in `._events` from above. - add `Position.clearsdict()` which returns the dict-ified and datetime-sorted table which can more-or-less be stored in the toml account file. - add `.minimized_clears()` a new (and close) version of the old method which always grabs at least one clear before a position-side-polarity-change. - mask-drop `.ensure_state()` since there is no more `.size`/`.price` state vars (per say) as we always re-calc the ppu and cumsize from the clears records on every read. - `.add_clear` no longer does bisec insorting since all sorting is done on position properties *reads*. - move the PPU (price per unit) calculator to a new `.accounting.calcs` as well as add in the `iter_by_dt()` clearing transaction sorted iterator. - also make some fixes to this to handle both lists of `Transaction` as well as `dict`s as before. - start rename of `PpTable` -> `Account` and make a note about adding a `.balances` table. - always `float()` the transaction size/price values since it seems if they get processed as `tomlkit.Integer` there's some suuper weird double negative on read-then-write to the clears table? - something like `cumsize = -1` -> `cumsize = --1` !?!? - make `load_pps_from_ledger()` work again but now includes some very very first draft `polars` df processing from a transaction ledger. - use this from the `accounting.cli.disect` subcmd which is also in *super early draft* mode ;) - obviously as mentioned in the `Position` section, add the new `.calcs` module with a `.ppu()` calculator func B)
2023-07-03 22:52:02 +00:00
def to_txns(
self,
symcache: SymbologyCache | None = None,
) -> dict[str, Transaction]:
'''
Return entire output from ``.iter_txns()`` in a ``dict``.
'''
txns: dict[str, Transaction] = {}
for t in self.iter_txns(symcache=symcache):
if not t:
log.warning(f'{self.mod.name}:{self.account} TXN is -> {t}')
continue
txns[t.tid] = t
return txns
Formalize transaction normalizer func signature Since each broker backend generally needs to define a specific field-name-schema to determine the exact instantiation arguments to `Transaction`, we generally need each backend to define an endpoint function to conduct this transaction from an input `dict[str, Any]` received either directly from provided ledger APIs or from previously stored `.accounting._ledger` saved trades ledger TOML files. To accomplish this we now require backends to declare a new routine: ```python def norm_trade( tid: str, # the uuid for the transaction txdict: dict, # the input record-dict # a table of mkt-symbols to backend # struct objects which define the (meta-data) for the backend specific # venue's symbology pairs: dict[str, Struct], ) -> Transaction: ... ``` which implements that record conversion (at least for trades) and can thus be used in `TransactionLedger.iter_txns()` which requires "some code" to implement the loading from a serialization format (aka the input `dict` record) to our local `Transaction` struct, normally also using a `Pair`-struct table defined (and maybe previously cached) by the specific backend such our (normalization layer's) `MktPair`'s fields can be set. For the case of our `.clearing._paper_engine` we def the routine to simply extract the exact same fields from the TOML ledger records that we previously had written (to it) and define it in that module. Also, we always pass `pairs=SymbologyCache.pairs: dict[str, Struct]` on norm trade calls such that offline ledger and accounting processing clients can use a previously cached symbology set without having to necessarily start the async-actor runtime to query the actual backend API if the data has already been saved locally on the system B) Other related: - always passthrough kwargs in overridden `.to_dict()` method. - only do fqme related trade record field name rewrites/names when operating on a paper ledger; normally a backend's records don't contain these. - fix `pendulum.DateTime` type annots. - just deliver `Transaction`s from `.iter_txns()`
2023-07-14 18:11:49 +00:00
def write_config(self) -> None:
'''
Render the self.data ledger dict to its TOML file form.
ALWAYS order datetime sorted!
'''
Formalize transaction normalizer func signature Since each broker backend generally needs to define a specific field-name-schema to determine the exact instantiation arguments to `Transaction`, we generally need each backend to define an endpoint function to conduct this transaction from an input `dict[str, Any]` received either directly from provided ledger APIs or from previously stored `.accounting._ledger` saved trades ledger TOML files. To accomplish this we now require backends to declare a new routine: ```python def norm_trade( tid: str, # the uuid for the transaction txdict: dict, # the input record-dict # a table of mkt-symbols to backend # struct objects which define the (meta-data) for the backend specific # venue's symbology pairs: dict[str, Struct], ) -> Transaction: ... ``` which implements that record conversion (at least for trades) and can thus be used in `TransactionLedger.iter_txns()` which requires "some code" to implement the loading from a serialization format (aka the input `dict` record) to our local `Transaction` struct, normally also using a `Pair`-struct table defined (and maybe previously cached) by the specific backend such our (normalization layer's) `MktPair`'s fields can be set. For the case of our `.clearing._paper_engine` we def the routine to simply extract the exact same fields from the TOML ledger records that we previously had written (to it) and define it in that module. Also, we always pass `pairs=SymbologyCache.pairs: dict[str, Struct]` on norm trade calls such that offline ledger and accounting processing clients can use a previously cached symbology set without having to necessarily start the async-actor runtime to query the actual backend API if the data has already been saved locally on the system B) Other related: - always passthrough kwargs in overridden `.to_dict()` method. - only do fqme related trade record field name rewrites/names when operating on a paper ledger; normally a backend's records don't contain these. - fix `pendulum.DateTime` type annots. - just deliver `Transaction`s from `.iter_txns()`
2023-07-14 18:11:49 +00:00
is_paper: bool = self.account == 'paper'
symcache: SymbologyCache = self._symcache
towrite: dict[str, Any] = {}
Rework `.accounting.Position` calcs to prep for `polars` We're probably going to move to implementing all accounting using `polars.DataFrame` and friends and thus this rejig preps for a much more "stateless" implementation of our `Position` type and its internal pos-accounting metrics: `ppu` and `cumsize`. Summary: - wrt to `._pos.Position`: - rename `.size`/`.accum_size` to `.cumsize` to be more in line with `polars.DataFrame.cumsum()`. - make `Position.expiry` delegate to the underlying `.mkt: MktPair` handling (hopefully) all edge cases.. - change over to a new `._events: dict[str, Transaction]` in prep for #510 (and friends) and enforce a new `Transaction.etype: str` which is by default `clear`. - add `.iter_by_type()` which iterates, filters and sorts the entries in `._events` from above. - add `Position.clearsdict()` which returns the dict-ified and datetime-sorted table which can more-or-less be stored in the toml account file. - add `.minimized_clears()` a new (and close) version of the old method which always grabs at least one clear before a position-side-polarity-change. - mask-drop `.ensure_state()` since there is no more `.size`/`.price` state vars (per say) as we always re-calc the ppu and cumsize from the clears records on every read. - `.add_clear` no longer does bisec insorting since all sorting is done on position properties *reads*. - move the PPU (price per unit) calculator to a new `.accounting.calcs` as well as add in the `iter_by_dt()` clearing transaction sorted iterator. - also make some fixes to this to handle both lists of `Transaction` as well as `dict`s as before. - start rename of `PpTable` -> `Account` and make a note about adding a `.balances` table. - always `float()` the transaction size/price values since it seems if they get processed as `tomlkit.Integer` there's some suuper weird double negative on read-then-write to the clears table? - something like `cumsize = -1` -> `cumsize = --1` !?!? - make `load_pps_from_ledger()` work again but now includes some very very first draft `polars` df processing from a transaction ledger. - use this from the `accounting.cli.disect` subcmd which is also in *super early draft* mode ;) - obviously as mentioned in the `Position` section, add the new `.calcs` module with a `.ppu()` calculator func B)
2023-07-03 22:52:02 +00:00
for tid, txdict in self.tx_sort(self.data.copy()):
# write blank-str expiry for non-expiring assets
if (
'expiry' in txdict
and txdict['expiry'] is None
):
Rework `.accounting.Position` calcs to prep for `polars` We're probably going to move to implementing all accounting using `polars.DataFrame` and friends and thus this rejig preps for a much more "stateless" implementation of our `Position` type and its internal pos-accounting metrics: `ppu` and `cumsize`. Summary: - wrt to `._pos.Position`: - rename `.size`/`.accum_size` to `.cumsize` to be more in line with `polars.DataFrame.cumsum()`. - make `Position.expiry` delegate to the underlying `.mkt: MktPair` handling (hopefully) all edge cases.. - change over to a new `._events: dict[str, Transaction]` in prep for #510 (and friends) and enforce a new `Transaction.etype: str` which is by default `clear`. - add `.iter_by_type()` which iterates, filters and sorts the entries in `._events` from above. - add `Position.clearsdict()` which returns the dict-ified and datetime-sorted table which can more-or-less be stored in the toml account file. - add `.minimized_clears()` a new (and close) version of the old method which always grabs at least one clear before a position-side-polarity-change. - mask-drop `.ensure_state()` since there is no more `.size`/`.price` state vars (per say) as we always re-calc the ppu and cumsize from the clears records on every read. - `.add_clear` no longer does bisec insorting since all sorting is done on position properties *reads*. - move the PPU (price per unit) calculator to a new `.accounting.calcs` as well as add in the `iter_by_dt()` clearing transaction sorted iterator. - also make some fixes to this to handle both lists of `Transaction` as well as `dict`s as before. - start rename of `PpTable` -> `Account` and make a note about adding a `.balances` table. - always `float()` the transaction size/price values since it seems if they get processed as `tomlkit.Integer` there's some suuper weird double negative on read-then-write to the clears table? - something like `cumsize = -1` -> `cumsize = --1` !?!? - make `load_pps_from_ledger()` work again but now includes some very very first draft `polars` df processing from a transaction ledger. - use this from the `accounting.cli.disect` subcmd which is also in *super early draft* mode ;) - obviously as mentioned in the `Position` section, add the new `.calcs` module with a `.ppu()` calculator func B)
2023-07-03 22:52:02 +00:00
txdict['expiry'] = ''
# (maybe) re-write old acro-key
if (
is_paper
# if symcache is empty/not supported (yet), don't
# bother xD
and symcache.mktmaps
):
Formalize transaction normalizer func signature Since each broker backend generally needs to define a specific field-name-schema to determine the exact instantiation arguments to `Transaction`, we generally need each backend to define an endpoint function to conduct this transaction from an input `dict[str, Any]` received either directly from provided ledger APIs or from previously stored `.accounting._ledger` saved trades ledger TOML files. To accomplish this we now require backends to declare a new routine: ```python def norm_trade( tid: str, # the uuid for the transaction txdict: dict, # the input record-dict # a table of mkt-symbols to backend # struct objects which define the (meta-data) for the backend specific # venue's symbology pairs: dict[str, Struct], ) -> Transaction: ... ``` which implements that record conversion (at least for trades) and can thus be used in `TransactionLedger.iter_txns()` which requires "some code" to implement the loading from a serialization format (aka the input `dict` record) to our local `Transaction` struct, normally also using a `Pair`-struct table defined (and maybe previously cached) by the specific backend such our (normalization layer's) `MktPair`'s fields can be set. For the case of our `.clearing._paper_engine` we def the routine to simply extract the exact same fields from the TOML ledger records that we previously had written (to it) and define it in that module. Also, we always pass `pairs=SymbologyCache.pairs: dict[str, Struct]` on norm trade calls such that offline ledger and accounting processing clients can use a previously cached symbology set without having to necessarily start the async-actor runtime to query the actual backend API if the data has already been saved locally on the system B) Other related: - always passthrough kwargs in overridden `.to_dict()` method. - only do fqme related trade record field name rewrites/names when operating on a paper ledger; normally a backend's records don't contain these. - fix `pendulum.DateTime` type annots. - just deliver `Transaction`s from `.iter_txns()`
2023-07-14 18:11:49 +00:00
fqme: str = txdict.pop('fqsn', None) or txdict['fqme']
bs_mktid: str | None = txdict.get('bs_mktid')
if (
fqme not in symcache.mktmaps
Formalize transaction normalizer func signature Since each broker backend generally needs to define a specific field-name-schema to determine the exact instantiation arguments to `Transaction`, we generally need each backend to define an endpoint function to conduct this transaction from an input `dict[str, Any]` received either directly from provided ledger APIs or from previously stored `.accounting._ledger` saved trades ledger TOML files. To accomplish this we now require backends to declare a new routine: ```python def norm_trade( tid: str, # the uuid for the transaction txdict: dict, # the input record-dict # a table of mkt-symbols to backend # struct objects which define the (meta-data) for the backend specific # venue's symbology pairs: dict[str, Struct], ) -> Transaction: ... ``` which implements that record conversion (at least for trades) and can thus be used in `TransactionLedger.iter_txns()` which requires "some code" to implement the loading from a serialization format (aka the input `dict` record) to our local `Transaction` struct, normally also using a `Pair`-struct table defined (and maybe previously cached) by the specific backend such our (normalization layer's) `MktPair`'s fields can be set. For the case of our `.clearing._paper_engine` we def the routine to simply extract the exact same fields from the TOML ledger records that we previously had written (to it) and define it in that module. Also, we always pass `pairs=SymbologyCache.pairs: dict[str, Struct]` on norm trade calls such that offline ledger and accounting processing clients can use a previously cached symbology set without having to necessarily start the async-actor runtime to query the actual backend API if the data has already been saved locally on the system B) Other related: - always passthrough kwargs in overridden `.to_dict()` method. - only do fqme related trade record field name rewrites/names when operating on a paper ledger; normally a backend's records don't contain these. - fix `pendulum.DateTime` type annots. - just deliver `Transaction`s from `.iter_txns()`
2023-07-14 18:11:49 +00:00
or (
# also try to see if this is maybe a paper
# engine ledger in which case the bs_mktid
# should be the fqme as well!
bs_mktid
and fqme != bs_mktid
)
Formalize transaction normalizer func signature Since each broker backend generally needs to define a specific field-name-schema to determine the exact instantiation arguments to `Transaction`, we generally need each backend to define an endpoint function to conduct this transaction from an input `dict[str, Any]` received either directly from provided ledger APIs or from previously stored `.accounting._ledger` saved trades ledger TOML files. To accomplish this we now require backends to declare a new routine: ```python def norm_trade( tid: str, # the uuid for the transaction txdict: dict, # the input record-dict # a table of mkt-symbols to backend # struct objects which define the (meta-data) for the backend specific # venue's symbology pairs: dict[str, Struct], ) -> Transaction: ... ``` which implements that record conversion (at least for trades) and can thus be used in `TransactionLedger.iter_txns()` which requires "some code" to implement the loading from a serialization format (aka the input `dict` record) to our local `Transaction` struct, normally also using a `Pair`-struct table defined (and maybe previously cached) by the specific backend such our (normalization layer's) `MktPair`'s fields can be set. For the case of our `.clearing._paper_engine` we def the routine to simply extract the exact same fields from the TOML ledger records that we previously had written (to it) and define it in that module. Also, we always pass `pairs=SymbologyCache.pairs: dict[str, Struct]` on norm trade calls such that offline ledger and accounting processing clients can use a previously cached symbology set without having to necessarily start the async-actor runtime to query the actual backend API if the data has already been saved locally on the system B) Other related: - always passthrough kwargs in overridden `.to_dict()` method. - only do fqme related trade record field name rewrites/names when operating on a paper ledger; normally a backend's records don't contain these. - fix `pendulum.DateTime` type annots. - just deliver `Transaction`s from `.iter_txns()`
2023-07-14 18:11:49 +00:00
):
# always take any (paper) bs_mktid if defined and
# in the backend's cache key set.
if bs_mktid in symcache.mktmaps:
Formalize transaction normalizer func signature Since each broker backend generally needs to define a specific field-name-schema to determine the exact instantiation arguments to `Transaction`, we generally need each backend to define an endpoint function to conduct this transaction from an input `dict[str, Any]` received either directly from provided ledger APIs or from previously stored `.accounting._ledger` saved trades ledger TOML files. To accomplish this we now require backends to declare a new routine: ```python def norm_trade( tid: str, # the uuid for the transaction txdict: dict, # the input record-dict # a table of mkt-symbols to backend # struct objects which define the (meta-data) for the backend specific # venue's symbology pairs: dict[str, Struct], ) -> Transaction: ... ``` which implements that record conversion (at least for trades) and can thus be used in `TransactionLedger.iter_txns()` which requires "some code" to implement the loading from a serialization format (aka the input `dict` record) to our local `Transaction` struct, normally also using a `Pair`-struct table defined (and maybe previously cached) by the specific backend such our (normalization layer's) `MktPair`'s fields can be set. For the case of our `.clearing._paper_engine` we def the routine to simply extract the exact same fields from the TOML ledger records that we previously had written (to it) and define it in that module. Also, we always pass `pairs=SymbologyCache.pairs: dict[str, Struct]` on norm trade calls such that offline ledger and accounting processing clients can use a previously cached symbology set without having to necessarily start the async-actor runtime to query the actual backend API if the data has already been saved locally on the system B) Other related: - always passthrough kwargs in overridden `.to_dict()` method. - only do fqme related trade record field name rewrites/names when operating on a paper ledger; normally a backend's records don't contain these. - fix `pendulum.DateTime` type annots. - just deliver `Transaction`s from `.iter_txns()`
2023-07-14 18:11:49 +00:00
fqme: str = bs_mktid
else:
best_fqme: str = list(symcache.search(fqme))[0]
Formalize transaction normalizer func signature Since each broker backend generally needs to define a specific field-name-schema to determine the exact instantiation arguments to `Transaction`, we generally need each backend to define an endpoint function to conduct this transaction from an input `dict[str, Any]` received either directly from provided ledger APIs or from previously stored `.accounting._ledger` saved trades ledger TOML files. To accomplish this we now require backends to declare a new routine: ```python def norm_trade( tid: str, # the uuid for the transaction txdict: dict, # the input record-dict # a table of mkt-symbols to backend # struct objects which define the (meta-data) for the backend specific # venue's symbology pairs: dict[str, Struct], ) -> Transaction: ... ``` which implements that record conversion (at least for trades) and can thus be used in `TransactionLedger.iter_txns()` which requires "some code" to implement the loading from a serialization format (aka the input `dict` record) to our local `Transaction` struct, normally also using a `Pair`-struct table defined (and maybe previously cached) by the specific backend such our (normalization layer's) `MktPair`'s fields can be set. For the case of our `.clearing._paper_engine` we def the routine to simply extract the exact same fields from the TOML ledger records that we previously had written (to it) and define it in that module. Also, we always pass `pairs=SymbologyCache.pairs: dict[str, Struct]` on norm trade calls such that offline ledger and accounting processing clients can use a previously cached symbology set without having to necessarily start the async-actor runtime to query the actual backend API if the data has already been saved locally on the system B) Other related: - always passthrough kwargs in overridden `.to_dict()` method. - only do fqme related trade record field name rewrites/names when operating on a paper ledger; normally a backend's records don't contain these. - fix `pendulum.DateTime` type annots. - just deliver `Transaction`s from `.iter_txns()`
2023-07-14 18:11:49 +00:00
log.warning(
f'Could not find FQME: {fqme} in qualified set?\n'
f'Qualifying and expanding {fqme} -> {best_fqme}'
)
fqme = best_fqme
if (
bs_mktid
and bs_mktid != fqme
):
# in paper account case always make sure both the
# fqme and bs_mktid are fully qualified..
txdict['bs_mktid'] = fqme
# in paper ledgers always write the latest
# symbology key field: an FQME.
txdict['fqme'] = fqme
Rework `.accounting.Position` calcs to prep for `polars` We're probably going to move to implementing all accounting using `polars.DataFrame` and friends and thus this rejig preps for a much more "stateless" implementation of our `Position` type and its internal pos-accounting metrics: `ppu` and `cumsize`. Summary: - wrt to `._pos.Position`: - rename `.size`/`.accum_size` to `.cumsize` to be more in line with `polars.DataFrame.cumsum()`. - make `Position.expiry` delegate to the underlying `.mkt: MktPair` handling (hopefully) all edge cases.. - change over to a new `._events: dict[str, Transaction]` in prep for #510 (and friends) and enforce a new `Transaction.etype: str` which is by default `clear`. - add `.iter_by_type()` which iterates, filters and sorts the entries in `._events` from above. - add `Position.clearsdict()` which returns the dict-ified and datetime-sorted table which can more-or-less be stored in the toml account file. - add `.minimized_clears()` a new (and close) version of the old method which always grabs at least one clear before a position-side-polarity-change. - mask-drop `.ensure_state()` since there is no more `.size`/`.price` state vars (per say) as we always re-calc the ppu and cumsize from the clears records on every read. - `.add_clear` no longer does bisec insorting since all sorting is done on position properties *reads*. - move the PPU (price per unit) calculator to a new `.accounting.calcs` as well as add in the `iter_by_dt()` clearing transaction sorted iterator. - also make some fixes to this to handle both lists of `Transaction` as well as `dict`s as before. - start rename of `PpTable` -> `Account` and make a note about adding a `.balances` table. - always `float()` the transaction size/price values since it seems if they get processed as `tomlkit.Integer` there's some suuper weird double negative on read-then-write to the clears table? - something like `cumsize = -1` -> `cumsize = --1` !?!? - make `load_pps_from_ledger()` work again but now includes some very very first draft `polars` df processing from a transaction ledger. - use this from the `accounting.cli.disect` subcmd which is also in *super early draft* mode ;) - obviously as mentioned in the `Position` section, add the new `.calcs` module with a `.ppu()` calculator func B)
2023-07-03 22:52:02 +00:00
towrite[tid] = txdict
with self.file_path.open(mode='wb') as fp:
tomli_w.dump(towrite, fp)
def load_ledger(
brokername: str,
acctid: str,
Formalize transaction normalizer func signature Since each broker backend generally needs to define a specific field-name-schema to determine the exact instantiation arguments to `Transaction`, we generally need each backend to define an endpoint function to conduct this transaction from an input `dict[str, Any]` received either directly from provided ledger APIs or from previously stored `.accounting._ledger` saved trades ledger TOML files. To accomplish this we now require backends to declare a new routine: ```python def norm_trade( tid: str, # the uuid for the transaction txdict: dict, # the input record-dict # a table of mkt-symbols to backend # struct objects which define the (meta-data) for the backend specific # venue's symbology pairs: dict[str, Struct], ) -> Transaction: ... ``` which implements that record conversion (at least for trades) and can thus be used in `TransactionLedger.iter_txns()` which requires "some code" to implement the loading from a serialization format (aka the input `dict` record) to our local `Transaction` struct, normally also using a `Pair`-struct table defined (and maybe previously cached) by the specific backend such our (normalization layer's) `MktPair`'s fields can be set. For the case of our `.clearing._paper_engine` we def the routine to simply extract the exact same fields from the TOML ledger records that we previously had written (to it) and define it in that module. Also, we always pass `pairs=SymbologyCache.pairs: dict[str, Struct]` on norm trade calls such that offline ledger and accounting processing clients can use a previously cached symbology set without having to necessarily start the async-actor runtime to query the actual backend API if the data has already been saved locally on the system B) Other related: - always passthrough kwargs in overridden `.to_dict()` method. - only do fqme related trade record field name rewrites/names when operating on a paper ledger; normally a backend's records don't contain these. - fix `pendulum.DateTime` type annots. - just deliver `Transaction`s from `.iter_txns()`
2023-07-14 18:11:49 +00:00
# for testing or manual load from file
dirpath: Path | None = None,
) -> tuple[dict, Path]:
'''
Load a ledger (TOML) file from user's config directory:
$CONFIG_DIR/accounting/ledgers/trades_<brokername>_<acctid>.toml
Return its `dict`-content and file path.
'''
import time
try:
import tomllib
except ModuleNotFoundError:
import tomli as tomllib
Formalize transaction normalizer func signature Since each broker backend generally needs to define a specific field-name-schema to determine the exact instantiation arguments to `Transaction`, we generally need each backend to define an endpoint function to conduct this transaction from an input `dict[str, Any]` received either directly from provided ledger APIs or from previously stored `.accounting._ledger` saved trades ledger TOML files. To accomplish this we now require backends to declare a new routine: ```python def norm_trade( tid: str, # the uuid for the transaction txdict: dict, # the input record-dict # a table of mkt-symbols to backend # struct objects which define the (meta-data) for the backend specific # venue's symbology pairs: dict[str, Struct], ) -> Transaction: ... ``` which implements that record conversion (at least for trades) and can thus be used in `TransactionLedger.iter_txns()` which requires "some code" to implement the loading from a serialization format (aka the input `dict` record) to our local `Transaction` struct, normally also using a `Pair`-struct table defined (and maybe previously cached) by the specific backend such our (normalization layer's) `MktPair`'s fields can be set. For the case of our `.clearing._paper_engine` we def the routine to simply extract the exact same fields from the TOML ledger records that we previously had written (to it) and define it in that module. Also, we always pass `pairs=SymbologyCache.pairs: dict[str, Struct]` on norm trade calls such that offline ledger and accounting processing clients can use a previously cached symbology set without having to necessarily start the async-actor runtime to query the actual backend API if the data has already been saved locally on the system B) Other related: - always passthrough kwargs in overridden `.to_dict()` method. - only do fqme related trade record field name rewrites/names when operating on a paper ledger; normally a backend's records don't contain these. - fix `pendulum.DateTime` type annots. - just deliver `Transaction`s from `.iter_txns()`
2023-07-14 18:11:49 +00:00
ldir: Path = (
dirpath
or
config._config_dir / 'accounting' / 'ledgers'
)
if not ldir.is_dir():
ldir.mkdir()
fname = f'trades_{brokername}_{acctid}.toml'
fpath: Path = ldir / fname
if not fpath.is_file():
log.info(
f'Creating new local trades ledger: {fpath}'
)
fpath.touch()
with fpath.open(mode='rb') as cf:
start = time.time()
ledger_dict = tomllib.load(cf)
log.debug(f'Ledger load took {time.time() - start}s')
return ledger_dict, fpath
@cm
def open_trade_ledger(
broker: str,
account: str,
allow_from_sync_code: bool = False,
symcache: SymbologyCache | None = None,
# default is to sort by detected datetime-ish field
tx_sort: Callable = iter_by_dt,
rewrite: bool = False,
Formalize transaction normalizer func signature Since each broker backend generally needs to define a specific field-name-schema to determine the exact instantiation arguments to `Transaction`, we generally need each backend to define an endpoint function to conduct this transaction from an input `dict[str, Any]` received either directly from provided ledger APIs or from previously stored `.accounting._ledger` saved trades ledger TOML files. To accomplish this we now require backends to declare a new routine: ```python def norm_trade( tid: str, # the uuid for the transaction txdict: dict, # the input record-dict # a table of mkt-symbols to backend # struct objects which define the (meta-data) for the backend specific # venue's symbology pairs: dict[str, Struct], ) -> Transaction: ... ``` which implements that record conversion (at least for trades) and can thus be used in `TransactionLedger.iter_txns()` which requires "some code" to implement the loading from a serialization format (aka the input `dict` record) to our local `Transaction` struct, normally also using a `Pair`-struct table defined (and maybe previously cached) by the specific backend such our (normalization layer's) `MktPair`'s fields can be set. For the case of our `.clearing._paper_engine` we def the routine to simply extract the exact same fields from the TOML ledger records that we previously had written (to it) and define it in that module. Also, we always pass `pairs=SymbologyCache.pairs: dict[str, Struct]` on norm trade calls such that offline ledger and accounting processing clients can use a previously cached symbology set without having to necessarily start the async-actor runtime to query the actual backend API if the data has already been saved locally on the system B) Other related: - always passthrough kwargs in overridden `.to_dict()` method. - only do fqme related trade record field name rewrites/names when operating on a paper ledger; normally a backend's records don't contain these. - fix `pendulum.DateTime` type annots. - just deliver `Transaction`s from `.iter_txns()`
2023-07-14 18:11:49 +00:00
# for testing or manual load from file
_fp: Path | None = None,
) -> Generator[TransactionLedger, None, None]:
'''
Indempotently create and read in a trade log file from the
``<configuration_dir>/ledgers/`` directory.
Files are named per broker account of the form
``<brokername>_<accountname>.toml``. The ``accountname`` here is the
name as defined in the user's ``brokers.toml`` config.
'''
from ..brokers import get_brokermod
mod: ModuleType = get_brokermod(broker)
Formalize transaction normalizer func signature Since each broker backend generally needs to define a specific field-name-schema to determine the exact instantiation arguments to `Transaction`, we generally need each backend to define an endpoint function to conduct this transaction from an input `dict[str, Any]` received either directly from provided ledger APIs or from previously stored `.accounting._ledger` saved trades ledger TOML files. To accomplish this we now require backends to declare a new routine: ```python def norm_trade( tid: str, # the uuid for the transaction txdict: dict, # the input record-dict # a table of mkt-symbols to backend # struct objects which define the (meta-data) for the backend specific # venue's symbology pairs: dict[str, Struct], ) -> Transaction: ... ``` which implements that record conversion (at least for trades) and can thus be used in `TransactionLedger.iter_txns()` which requires "some code" to implement the loading from a serialization format (aka the input `dict` record) to our local `Transaction` struct, normally also using a `Pair`-struct table defined (and maybe previously cached) by the specific backend such our (normalization layer's) `MktPair`'s fields can be set. For the case of our `.clearing._paper_engine` we def the routine to simply extract the exact same fields from the TOML ledger records that we previously had written (to it) and define it in that module. Also, we always pass `pairs=SymbologyCache.pairs: dict[str, Struct]` on norm trade calls such that offline ledger and accounting processing clients can use a previously cached symbology set without having to necessarily start the async-actor runtime to query the actual backend API if the data has already been saved locally on the system B) Other related: - always passthrough kwargs in overridden `.to_dict()` method. - only do fqme related trade record field name rewrites/names when operating on a paper ledger; normally a backend's records don't contain these. - fix `pendulum.DateTime` type annots. - just deliver `Transaction`s from `.iter_txns()`
2023-07-14 18:11:49 +00:00
ledger_dict, fpath = load_ledger(
broker,
account,
dirpath=_fp,
)
cpy = ledger_dict.copy()
# XXX NOTE: if not provided presume we are being called from
# sync code and need to maybe run `trio` to generate..
if symcache is None:
# XXX: be mega pendantic and ensure the caller knows what
# they're doing!
if not allow_from_sync_code:
raise RuntimeError(
'You MUST set `allow_from_sync_code=True` when '
'calling `open_trade_ledger()` from sync code! '
'If you are calling from async code you MUST '
'instead pass a `symcache: SymbologyCache`!'
)
from ..data._symcache import (
get_symcache,
)
symcache: SymbologyCache = get_symcache(broker)
assert symcache
ledger = TransactionLedger(
ledger_dict=cpy,
file_path=fpath,
account=account,
mod=mod,
symcache=symcache,
tx_sort=getattr(mod, 'tx_sort', tx_sort),
)
try:
yield ledger
finally:
if (
ledger.data != ledger_dict
or rewrite
):
# TODO: show diff output?
# https://stackoverflow.com/questions/12956957/print-diff-of-python-dictionaries
log.info(f'Updating ledger for {fpath}:\n')
ledger.write_config()