Move cum-calcs to `open_ledger_dfs()`, always parse `str`->`Datetime`
Previously the cum-size calc(s) was in the `disect` CLI but it's better stuffed into the backing df converter. Also, ensure that whenever a `dt` field is type-detected as a `str` we parse it to `DateTime`.account_tests
parent
69314e9fca
commit
3d20490ee5
|
@ -20,6 +20,7 @@ you know when you're losing money (if possible) XD
|
||||||
|
|
||||||
'''
|
'''
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
from collections.abc import ValuesView
|
||||||
from contextlib import contextmanager as cm
|
from contextlib import contextmanager as cm
|
||||||
from math import copysign
|
from math import copysign
|
||||||
from typing import (
|
from typing import (
|
||||||
|
@ -39,6 +40,7 @@ from pendulum import (
|
||||||
if TYPE_CHECKING:
|
if TYPE_CHECKING:
|
||||||
from ._ledger import (
|
from ._ledger import (
|
||||||
Transaction,
|
Transaction,
|
||||||
|
TransactionLedger,
|
||||||
)
|
)
|
||||||
|
|
||||||
def ppu(
|
def ppu(
|
||||||
|
@ -72,18 +74,23 @@ def ppu(
|
||||||
where `cost_basis` for the current step is simply the price
|
where `cost_basis` for the current step is simply the price
|
||||||
* size of the most recent clearing transaction.
|
* size of the most recent clearing transaction.
|
||||||
|
|
||||||
|
-----
|
||||||
|
TODO: get the BEP computed and working similarly!
|
||||||
|
-----
|
||||||
|
the equivalent "break even price" or bep at each new clear
|
||||||
|
event step conversely only changes when an "position exiting
|
||||||
|
clear" which **decreases** the cumulative dst asset size:
|
||||||
|
|
||||||
|
bep[-1] = ppu[-1] - (cum_pnl[-1] / cumsize[-1])
|
||||||
|
|
||||||
'''
|
'''
|
||||||
asize_h: list[float] = [] # historical accumulative size
|
asize_h: list[float] = [] # historical accumulative size
|
||||||
ppu_h: list[float] = [] # historical price-per-unit
|
ppu_h: list[float] = [] # historical price-per-unit
|
||||||
ledger: dict[str, dict] = {}
|
ledger: dict[str, dict] = {}
|
||||||
|
|
||||||
# entry: dict[str, Any] | Transaction
|
|
||||||
t: Transaction
|
t: Transaction
|
||||||
for t in clears:
|
for t in clears:
|
||||||
# tid: str = entry['tid']
|
|
||||||
# clear_size = entry['size']
|
|
||||||
clear_size: float = t.size
|
clear_size: float = t.size
|
||||||
# clear_price: str | float = entry['price']
|
|
||||||
clear_price: str | float = t.price
|
clear_price: str | float = t.price
|
||||||
is_clear: bool = not isinstance(clear_price, str)
|
is_clear: bool = not isinstance(clear_price, str)
|
||||||
|
|
||||||
|
@ -152,7 +159,6 @@ def ppu(
|
||||||
clear_price * abs(clear_size)
|
clear_price * abs(clear_size)
|
||||||
+
|
+
|
||||||
# transaction cost
|
# transaction cost
|
||||||
# accum_sign * cost_scalar * entry['cost']
|
|
||||||
accum_sign * cost_scalar * t.cost
|
accum_sign * cost_scalar * t.cost
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -187,13 +193,13 @@ def ppu(
|
||||||
asize_h.append(accum_size)
|
asize_h.append(accum_size)
|
||||||
|
|
||||||
# ledger[t.tid] = {
|
# ledger[t.tid] = {
|
||||||
# 'tx': t,
|
# 'txn': t,
|
||||||
ledger[t.tid] = t.to_dict() | {
|
ledger[t.tid] = t.to_dict() | {
|
||||||
'ppu': ppu,
|
'ppu': ppu,
|
||||||
'cumsize': accum_size,
|
'cumsize': accum_size,
|
||||||
'sign_change': sign_change,
|
'sign_change': sign_change,
|
||||||
|
|
||||||
# TODO: cumpnl, bep
|
# TODO: cum_pnl, bep
|
||||||
}
|
}
|
||||||
|
|
||||||
final_ppu = ppu_h[-1] if ppu_h else 0
|
final_ppu = ppu_h[-1] if ppu_h else 0
|
||||||
|
@ -212,6 +218,7 @@ def ppu(
|
||||||
def iter_by_dt(
|
def iter_by_dt(
|
||||||
records: (
|
records: (
|
||||||
dict[str, dict[str, Any]]
|
dict[str, dict[str, Any]]
|
||||||
|
| ValuesView[dict] # eg. `Position._events.values()`
|
||||||
| list[dict]
|
| list[dict]
|
||||||
| list[Transaction] # XXX preferred!
|
| list[Transaction] # XXX preferred!
|
||||||
),
|
),
|
||||||
|
@ -220,7 +227,7 @@ def iter_by_dt(
|
||||||
# so if you know that the record stats show some field
|
# so if you know that the record stats show some field
|
||||||
# is more common then others, stick it at the top B)
|
# is more common then others, stick it at the top B)
|
||||||
parsers: dict[str, Callable | None] = {
|
parsers: dict[str, Callable | None] = {
|
||||||
'dt': None, # parity case
|
'dt': parse, # parity case
|
||||||
'datetime': parse, # datetime-str
|
'datetime': parse, # datetime-str
|
||||||
'time': from_timestamp, # float epoch
|
'time': from_timestamp, # float epoch
|
||||||
},
|
},
|
||||||
|
@ -259,8 +266,13 @@ def iter_by_dt(
|
||||||
# the `parsers` table above (when NOT using
|
# the `parsers` table above (when NOT using
|
||||||
# `.get()`), otherwise pass through the value and
|
# `.get()`), otherwise pass through the value and
|
||||||
# sort on it directly
|
# sort on it directly
|
||||||
parser: Callable | None = parsers[k]
|
if (
|
||||||
return parser(v) if (parser is not None) else v
|
not isinstance(v, DateTime)
|
||||||
|
and (parser := parsers.get(k))
|
||||||
|
):
|
||||||
|
return parser(v)
|
||||||
|
else:
|
||||||
|
return v
|
||||||
|
|
||||||
else:
|
else:
|
||||||
# XXX: should never get here..
|
# XXX: should never get here..
|
||||||
|
@ -271,6 +283,7 @@ def iter_by_dt(
|
||||||
records,
|
records,
|
||||||
key=key or dyn_parse_to_dt,
|
key=key or dyn_parse_to_dt,
|
||||||
):
|
):
|
||||||
|
# NOTE the type sig above; either pairs or txns B)
|
||||||
yield entry
|
yield entry
|
||||||
|
|
||||||
|
|
||||||
|
@ -331,7 +344,14 @@ def open_ledger_dfs(
|
||||||
brokername: str,
|
brokername: str,
|
||||||
acctname: str,
|
acctname: str,
|
||||||
|
|
||||||
) -> dict[str, pl.DataFrame]:
|
ledger: TransactionLedger | None = None,
|
||||||
|
|
||||||
|
**kwargs,
|
||||||
|
|
||||||
|
) -> tuple[
|
||||||
|
dict[str, pl.DataFrame],
|
||||||
|
TransactionLedger,
|
||||||
|
]:
|
||||||
'''
|
'''
|
||||||
Open a ledger of trade records (presumably from some broker
|
Open a ledger of trade records (presumably from some broker
|
||||||
backend), normalize the records into `Transactions` via the
|
backend), normalize the records into `Transactions` via the
|
||||||
|
@ -341,86 +361,89 @@ def open_ledger_dfs(
|
||||||
'''
|
'''
|
||||||
from ._ledger import (
|
from ._ledger import (
|
||||||
open_trade_ledger,
|
open_trade_ledger,
|
||||||
# Transaction,
|
|
||||||
TransactionLedger,
|
|
||||||
)
|
)
|
||||||
|
|
||||||
ledger: TransactionLedger
|
if not ledger:
|
||||||
import time
|
import time
|
||||||
now = time.time()
|
from tractor._debug import open_crash_handler
|
||||||
with (
|
now = time.time()
|
||||||
open_trade_ledger(
|
|
||||||
brokername,
|
|
||||||
acctname,
|
|
||||||
rewrite=True,
|
|
||||||
allow_from_sync_code=True,
|
|
||||||
) as ledger,
|
|
||||||
):
|
|
||||||
if not ledger:
|
|
||||||
raise ValueError(f'No ledger for {acctname}@{brokername} exists?')
|
|
||||||
|
|
||||||
print(f'LEDGER LOAD TIME: {time.time() - now}')
|
with (
|
||||||
# process raw TOML ledger into txns using the
|
open_crash_handler(),
|
||||||
# appropriate backend normalizer.
|
|
||||||
# cache: AssetsInfo = get_symcache(
|
|
||||||
# brokername,
|
|
||||||
# allow_reload=True,
|
|
||||||
# )
|
|
||||||
|
|
||||||
txns: dict[str, Transaction]
|
open_trade_ledger(
|
||||||
if acctname != 'paper':
|
brokername,
|
||||||
txns = ledger.mod.norm_trade_records(ledger)
|
acctname,
|
||||||
else:
|
rewrite=True,
|
||||||
txns = ledger.to_txns()
|
allow_from_sync_code=True,
|
||||||
|
|
||||||
ldf = pl.DataFrame(
|
# proxied through from caller
|
||||||
list(txn.to_dict() for txn in txns.values()),
|
**kwargs,
|
||||||
# schema=[
|
|
||||||
# ('tid', str),
|
) as ledger,
|
||||||
# ('fqme', str),
|
):
|
||||||
# ('dt', str),
|
if not ledger:
|
||||||
# ('size', pl.Float64),
|
raise ValueError(f'No ledger for {acctname}@{brokername} exists?')
|
||||||
# ('price', pl.Float64),
|
|
||||||
# ('cost', pl.Float64),
|
print(f'LEDGER LOAD TIME: {time.time() - now}')
|
||||||
# ('expiry', str),
|
|
||||||
# ('bs_mktid', str),
|
# process raw TOML ledger into txns using the
|
||||||
# ],
|
# appropriate backend normalizer.
|
||||||
).sort('dt').select([
|
# cache: AssetsInfo = get_symcache(
|
||||||
pl.col('fqme'),
|
# brokername,
|
||||||
pl.col('dt').str.to_datetime(),
|
# allow_reload=True,
|
||||||
# pl.col('expiry').dt.datetime(),
|
# )
|
||||||
pl.col('bs_mktid'),
|
|
||||||
pl.col('size'),
|
txns: dict[str, Transaction] = ledger.to_txns()
|
||||||
pl.col('price'),
|
ldf = pl.DataFrame(
|
||||||
|
list(txn.to_dict() for txn in txns.values()),
|
||||||
|
# schema=[
|
||||||
|
# ('tid', str),
|
||||||
|
# ('fqme', str),
|
||||||
|
# ('dt', str),
|
||||||
|
# ('size', pl.Float64),
|
||||||
|
# ('price', pl.Float64),
|
||||||
|
# ('cost', pl.Float64),
|
||||||
|
# ('expiry', str),
|
||||||
|
# ('bs_mktid', str),
|
||||||
|
# ],
|
||||||
|
# ).sort('dt').select([
|
||||||
|
).sort('dt').with_columns([
|
||||||
|
# pl.col('fqme'),
|
||||||
|
pl.col('dt').str.to_datetime(),
|
||||||
|
# pl.col('expiry').dt.datetime(),
|
||||||
|
# pl.col('bs_mktid'),
|
||||||
|
# pl.col('size'),
|
||||||
|
# pl.col('price'),
|
||||||
|
])
|
||||||
|
|
||||||
|
# filter out to the columns matching values filter passed
|
||||||
|
# as input.
|
||||||
|
# if filter_by_ids:
|
||||||
|
# for col, vals in filter_by_ids.items():
|
||||||
|
# str_vals = set(map(str, vals))
|
||||||
|
# pred: pl.Expr = pl.col(col).eq(str_vals.pop())
|
||||||
|
# for val in str_vals:
|
||||||
|
# pred |= pl.col(col).eq(val)
|
||||||
|
|
||||||
|
# fdf = df.filter(pred)
|
||||||
|
|
||||||
|
# bs_mktid: str = fdf[0]['bs_mktid']
|
||||||
|
# pos: Position = acnt.pps[bs_mktid]
|
||||||
|
|
||||||
|
# TODO: not sure if this is even possible but..
|
||||||
|
# ppt = df.groupby('fqme').agg([
|
||||||
|
# # TODO: ppu and bep !!
|
||||||
|
# pl.cumsum('size').alias('cumsum'),
|
||||||
|
# ])
|
||||||
|
dfs: dict[str, pl.DataFrame] = ldf.partition_by(
|
||||||
|
'fqme',
|
||||||
|
as_dict=True,
|
||||||
|
)
|
||||||
|
for key in dfs:
|
||||||
|
df = dfs[key]
|
||||||
|
dfs[key] = df.with_columns([
|
||||||
|
pl.cumsum('size').alias('cumsize'),
|
||||||
])
|
])
|
||||||
|
|
||||||
# filter out to the columns matching values filter passed
|
yield dfs, ledger
|
||||||
# as input.
|
|
||||||
# if filter_by_ids:
|
|
||||||
# for col, vals in filter_by_ids.items():
|
|
||||||
# str_vals = set(map(str, vals))
|
|
||||||
# pred: pl.Expr = pl.col(col).eq(str_vals.pop())
|
|
||||||
# for val in str_vals:
|
|
||||||
# pred |= pl.col(col).eq(val)
|
|
||||||
|
|
||||||
# fdf = df.filter(pred)
|
|
||||||
|
|
||||||
# bs_mktid: str = fdf[0]['bs_mktid']
|
|
||||||
# pos: Position = acnt.pps[bs_mktid]
|
|
||||||
|
|
||||||
# ppt = df.groupby('fqme').agg([
|
|
||||||
# # TODO: ppu and bep !!
|
|
||||||
# pl.cumsum('size').alias('cumsum'),
|
|
||||||
# ])
|
|
||||||
|
|
||||||
dfs: dict[str, pl.DataFrame] = ldf.partition_by(
|
|
||||||
'fqme',
|
|
||||||
as_dict=True,
|
|
||||||
)
|
|
||||||
|
|
||||||
# for fqme, ppt in act.items():
|
|
||||||
# ppt.with_columns
|
|
||||||
# # TODO: ppu and bep !!
|
|
||||||
# pl.cumsum('size').alias('cumsum'),
|
|
||||||
# ])
|
|
||||||
yield dfs
|
|
||||||
|
|
|
@ -37,8 +37,8 @@ from ..calc import humanize
|
||||||
from ..brokers._daemon import broker_init
|
from ..brokers._daemon import broker_init
|
||||||
from ._ledger import (
|
from ._ledger import (
|
||||||
load_ledger,
|
load_ledger,
|
||||||
|
TransactionLedger,
|
||||||
# open_trade_ledger,
|
# open_trade_ledger,
|
||||||
# TransactionLedger,
|
|
||||||
)
|
)
|
||||||
from .calc import (
|
from .calc import (
|
||||||
open_ledger_dfs,
|
open_ledger_dfs,
|
||||||
|
@ -263,20 +263,17 @@ def disect(
|
||||||
|
|
||||||
# ledger dfs groupby-partitioned by fqme
|
# ledger dfs groupby-partitioned by fqme
|
||||||
dfs: dict[str, pl.DataFrame]
|
dfs: dict[str, pl.DataFrame]
|
||||||
|
# actual ledger ref filled in with all txns
|
||||||
|
ldgr: TransactionLedger
|
||||||
|
|
||||||
with open_ledger_dfs(
|
with open_ledger_dfs(
|
||||||
brokername,
|
brokername,
|
||||||
account,
|
account,
|
||||||
) as dfs:
|
) as (dfs, ldgr):
|
||||||
|
|
||||||
for key in dfs:
|
# look up specific frame for fqme-selected asset
|
||||||
df = dfs[key]
|
df = dfs[fqme]
|
||||||
dfs[key] = df.with_columns([
|
|
||||||
pl.cumsum('size').alias('cumsum'),
|
|
||||||
])
|
|
||||||
|
|
||||||
ppt = dfs[fqme]
|
|
||||||
assert not df.is_empty()
|
assert not df.is_empty()
|
||||||
assert not ppt.is_empty()
|
|
||||||
|
|
||||||
# TODO: we REALLY need a better console REPL for this
|
# TODO: we REALLY need a better console REPL for this
|
||||||
# kinda thing..
|
# kinda thing..
|
||||||
|
|
Loading…
Reference in New Issue