Handle txn costs in BEP, factor enter/exit blocks and df row assignments B)
parent
29bab02c64
commit
b6a705852d
|
@ -386,12 +386,18 @@ def open_ledger_dfs(
|
|||
# allow_reload=True,
|
||||
# )
|
||||
|
||||
yield ledger_to_dfs(ledger), ledger
|
||||
yield ledger_to_dfs(ledger), ledger
|
||||
|
||||
|
||||
def ledger_to_dfs(
|
||||
ledger: TransactionLedger,
|
||||
|
||||
# include transaction cost in breakeven price
|
||||
# and presume the worst case of the same cost
|
||||
# to exit this transaction (even though in reality
|
||||
# it will be dynamic based on exit stratetgy).
|
||||
cost_scalar: float = 1,
|
||||
|
||||
) -> dict[str, pl.DataFrame]:
|
||||
|
||||
txns: dict[str, Transaction] = ledger.to_txns()
|
||||
|
@ -462,12 +468,16 @@ def ledger_to_dfs(
|
|||
# the market) to acquire the dst asset, PER txn.
|
||||
# when this value is -ve (i.e. a sell operation) then
|
||||
# the amount sent is actually "returned".
|
||||
(pl.col('price') * pl.col('size')).alias('dst_bot'),
|
||||
(
|
||||
(pl.col('price') * pl.col('size'))
|
||||
+
|
||||
pl.col('cost')
|
||||
).alias('dst_bot'),
|
||||
|
||||
]).with_columns([
|
||||
|
||||
# rolling balance in src asset units
|
||||
(pl.cumsum('dst_bot') * -1).alias('src_balance'),
|
||||
(pl.col('dst_bot').cumsum() * -1).alias('src_balance'),
|
||||
|
||||
# "position operation type" in terms of increasing the
|
||||
# amount in the dst asset (entering) or decreasing the
|
||||
|
@ -506,7 +516,7 @@ def ledger_to_dfs(
|
|||
]).select([
|
||||
pl.exclude([
|
||||
'tid',
|
||||
'dt',
|
||||
# 'dt',
|
||||
'expiry',
|
||||
'bs_mktid',
|
||||
'etype',
|
||||
|
@ -518,114 +528,137 @@ def ledger_to_dfs(
|
|||
last_cumsize: float = 0
|
||||
last_ledger_pnl: float = 0
|
||||
last_pos_pnl: float = 0
|
||||
last_is_enter: bool = False
|
||||
# last_is_enter: bool = False # TODO: drop right?
|
||||
|
||||
# imperatively compute the PPU (price per unit) and BEP
|
||||
# (break even price) iteratively over the ledger, oriented
|
||||
# to each position state.
|
||||
# around each position state: a state of split balances in
|
||||
# > 1 asset.
|
||||
for i, row in enumerate(df.iter_rows(named=True)):
|
||||
|
||||
cumsize: float = row['cumsize']
|
||||
is_enter: bool = row['is_enter']
|
||||
price: float = row['price']
|
||||
size: float = row['size']
|
||||
|
||||
# ALWAYS reset per-position cum PnL
|
||||
if last_cumsize == 0:
|
||||
last_pos_pnl: float = 0
|
||||
|
||||
# a "position size INCREASING" transaction which "makes
|
||||
# larger", in src asset unit terms, the trade's
|
||||
# side-size of the destination asset:
|
||||
# the profit is ALWAYS decreased, aka made a "loss"
|
||||
# by the constant fee charged by the txn provider!
|
||||
# TODO: support exit txn virtual cost which we
|
||||
# resolve on exit txns incrementally?
|
||||
pnl: float = -1 * row['cost']
|
||||
|
||||
# a "position size INCREASING" or ENTER transaction
|
||||
# which "makes larger", in src asset unit terms, the
|
||||
# trade's side-size of the destination asset:
|
||||
# - "buying" (more) units of the dst asset
|
||||
# - "selling" (more short) units of the dst asset
|
||||
if is_enter:
|
||||
|
||||
# a cumulative mean of the price-per-unit acquired
|
||||
# in the destination asset:
|
||||
# https://en.wikipedia.org/wiki/Moving_average#Cumulative_average
|
||||
# You could also think of this measure more
|
||||
# generally as an exponential mean with `alpha
|
||||
# = 1/N` where `N` is the current number of txns
|
||||
# included in the "position" defining set:
|
||||
# https://en.wikipedia.org/wiki/Exponential_smoothing
|
||||
ppu: float = (
|
||||
(
|
||||
(last_ppu * last_cumsize)
|
||||
+
|
||||
(row['price'] * row['size'])
|
||||
(price * size)
|
||||
) /
|
||||
cumsize
|
||||
)
|
||||
|
||||
pos_bep: float = ppu
|
||||
# When we "enter more" dst asset units (increase
|
||||
# position state) AFTER having exitted some units
|
||||
# the bep needs to be RECOMPUTED based on new ppu
|
||||
# such that liquidation of the cumsize at the bep
|
||||
# price results in a zero-pnl for the existing
|
||||
# position (since the last one).
|
||||
if (
|
||||
not last_is_enter
|
||||
and last_cumsize != 0
|
||||
):
|
||||
|
||||
pos_bep: float = (
|
||||
(
|
||||
(ppu * cumsize)
|
||||
-
|
||||
last_pos_pnl
|
||||
)
|
||||
/
|
||||
cumsize
|
||||
)
|
||||
|
||||
df[i, 'ledger_bep'] = df[i, 'pos_bep'] = pos_bep
|
||||
|
||||
# a "position size DECREASING" transaction which "makes
|
||||
# smaller" the trade's side-size of the destination
|
||||
# asset:
|
||||
# a "position size DECREASING" or EXIT transaction
|
||||
# which "makes smaller" the trade's side-size of the
|
||||
# destination asset:
|
||||
# - selling previously bought units of the dst asset
|
||||
# (aka 'closing' a long position).
|
||||
# - buying previously borrowed and sold (short) units
|
||||
# of the dst asset (aka 'covering'/'closing' a short
|
||||
# position).
|
||||
else:
|
||||
# only changes on position size increasing txns
|
||||
ppu: float = last_ppu
|
||||
|
||||
pnl = df[i, 'per_exit_pnl'] = (
|
||||
(last_ppu - row['price'])
|
||||
*
|
||||
row['size']
|
||||
# include the per-txn profit or loss given we are
|
||||
# "closing" the position with this txn.
|
||||
pnl += (last_ppu - price) * size
|
||||
|
||||
# cumulative PnLs per txn
|
||||
last_ledger_pnl = (
|
||||
last_ledger_pnl + pnl
|
||||
)
|
||||
last_pos_pnl = df[i, 'cum_pos_pnl'] = (
|
||||
last_pos_pnl + pnl
|
||||
)
|
||||
|
||||
if cumsize == 0:
|
||||
last_ppu = ppu = 0
|
||||
|
||||
# compute the "break even price" that
|
||||
# when the remaining cumsize is liquidated at
|
||||
# this price the net-pnl on the current position
|
||||
# will result in ZERO pnl from open to close B)
|
||||
if (
|
||||
abs(cumsize) > 0 # non-exit-to-zero position txn
|
||||
):
|
||||
|
||||
ledger_bep = pos_bep = ppu
|
||||
|
||||
# TODO: now that this
|
||||
# recalc-bep-on-enters-based-on-new-ppu was
|
||||
# factored out of the `is_enter` block above we can
|
||||
# drop this if condition right?
|
||||
#
|
||||
# When we "enter more" dst asset units (aka
|
||||
# increase position state) AFTER having exited some
|
||||
# units (aka decreasing the pos size some) the bep
|
||||
# needs to be RECOMPUTED based on new ppu such that
|
||||
# liquidation of the cumsize at the bep price
|
||||
# results in a zero-pnl for the existing position
|
||||
# (since the last one).
|
||||
# if (
|
||||
# not last_is_enter
|
||||
# and last_cumsize != 0
|
||||
# ):
|
||||
|
||||
ledger_bep: float = (
|
||||
(
|
||||
(ppu * cumsize)
|
||||
-
|
||||
(last_ledger_pnl * copysign(1, cumsize))
|
||||
) / cumsize
|
||||
)
|
||||
|
||||
last_ledger_pnl = df[i, 'cum_ledger_pnl'] = last_ledger_pnl + pnl
|
||||
|
||||
last_pos_pnl = df[i, 'cum_pos_pnl'] = last_pos_pnl + pnl
|
||||
|
||||
if cumsize == 0:
|
||||
ppu: float = 0
|
||||
last_ppu: float = 0
|
||||
else:
|
||||
ppu: float = last_ppu
|
||||
|
||||
|
||||
if abs(cumsize) > 0:
|
||||
# compute the "break even price" that
|
||||
# when the remaining cumsize is liquidated at
|
||||
# this price the net-pnl on the current position
|
||||
# will result in ZERO pnl from open to close B)
|
||||
ledger_bep: float = (
|
||||
(
|
||||
(ppu * cumsize)
|
||||
-
|
||||
last_ledger_pnl
|
||||
) / cumsize
|
||||
)
|
||||
df[i, 'ledger_bep'] = ledger_bep
|
||||
|
||||
pos_bep: float = (
|
||||
(
|
||||
(ppu * cumsize)
|
||||
-
|
||||
last_pos_pnl
|
||||
) / cumsize
|
||||
)
|
||||
df[i, 'pos_bep'] = pos_bep
|
||||
# for position lifetime BEP we never can have
|
||||
# a valid value once the position is "closed"
|
||||
# / full exitted Bo
|
||||
pos_bep: float = (
|
||||
(
|
||||
(ppu * cumsize)
|
||||
-
|
||||
(last_pos_pnl * copysign(1, cumsize))
|
||||
) / cumsize
|
||||
)
|
||||
|
||||
# inject DF row with all values
|
||||
df[i, 'pos_ppu'] = ppu
|
||||
df[i, 'per_exit_pnl'] = pnl
|
||||
df[i, 'cum_pos_pnl'] = last_pos_pnl
|
||||
df[i, 'pos_bep'] = pos_bep
|
||||
df[i, 'cum_ledger_pnl'] = last_ledger_pnl
|
||||
df[i, 'ledger_bep'] = ledger_bep
|
||||
|
||||
# keep backrefs to suffice reccurence relation
|
||||
last_ppu: float = ppu
|
||||
last_cumsize: float = cumsize
|
||||
last_is_enter: bool = is_enter
|
||||
# last_is_enter: bool = is_enter # TODO: drop right?
|
||||
|
||||
return dfs
|
||||
|
|
Loading…
Reference in New Issue