General theory
Let we have some investments in strategy and we want to calculate ROI. If we don't make any withdrawals or deposits then it is easy to calculate ROI = NAV / initial investments - 1
. However, if we make portfolio rebalancing or new deposits/withdrawals we should account for them and simple ROI formula is not enough here.
For this purpose we can use the following algorithm:
-
Create virtual pif with shares = deposited asset (in shares) and set share price
P = 1
. -
Any deposit or withdrawal is equivalent to buying or selling some shares per share price
P_T0
.Let
X
was added to virtual pif at timeT
, whereX > 0
when we make a deposit, andX < 0
when we make a withdrawal;T0 = T - eps
- timestamp before transactionT1 = T + eps
- timestamp after transactionpif consisted of
N
SHARES with share priceP_0 = NAV_T0 / N
.New shares amount will be
M = N + X / P_0
Updated share priceP_T1 = NAV_T1 / M
-
So, for each moment we have our
NAV_t
, virtual shares amountN_t
and share priceP_t = NAV_t / N_t
and we can calculate ROI asP_t / P_t0 - 1
Moreover, we can calculate ROI at any period(t, t0)
,t > t0
.
- Create own class
Investor
implemented by Investor interface and redefineget_nav_by_timestamp
- it can bepandas.Series
with access to NAV by timestamp or manual input like in the example (not recommended)
class ExampleInvestor(Investor):
'''
Simple lending (static) strategy with 0.05% profit daily
on investments without reinvestment
'''
def __init__(self, investment_timestamp, deposit, transactions):
super().__init__(investment_timestamp, deposit, transactions)
def lending_assets(self, timestamp):
# before transaction
if timestamp <= datetime(2020, 4, 1):
return 100
# after transaction
else:
return 300
def get_nav_by_timestamp(self, timestamp):
'''
NAV = investments + PnL
daily PnL = 0.0005 * investments =>
total PnL = 0.0005 * sum(invesmetns_i * period_i)
'''
if timestamp < datetime(2020, 4, 1):
pnl = 0.0005 * \
self.lending_assets(timestamp) * \
(timestamp - self.investment_timestamp).days
return self.lending_assets(timestamp) + pnl
elif timestamp > datetime(2020, 4, 1):
# redefine investments_i and daily PnL
transaction_timestamp = datetime(2020, 4, 1)
acc_pnl_before_transaction = 0.0005 * self.lending_assets(
transaction_timestamp) * (transaction_timestamp - self.investment_timestamp).days
pnl = 0.0005 * self.lending_assets(timestamp) * (timestamp - transaction_timestamp).days +\
acc_pnl_before_transaction
return self.lending_assets(timestamp) + pnl
- In this example, there is a simple strategy model with 0.05% daily returns on investments. Let we have deposit at 2020/4/1 then our NAV = current_investments + PnL_before_deposit + PnL_after_deposit.
Define transactions:
transaction = Transaction(datetime(2020, 4, 1), funding=200)
Create pif object:
investor = ExampleInvestor(investment_timestamp=datetime(2020, 1, 1),
deposit=100, transactions=[transaction])
# create pif
pif = ROICalculator(investor)
- Calculate ROI per period
(t_0, t_1)
,t_1 > t_0
# initial investment time
t_0 = pif.investor.investment_timestamp
#
# before transaction
#
t_1 = datetime(2020, 3, 31)
return_day_1 = pif.get_share_price_perfomance(t=t_1,
t0=t_1 - timedelta(days=1))
print(f'1D return on {t_1.date()} = {return_day_1 * 100:.2f} %')
return_mtd_1 = pif.get_share_price_perfomance(t=t_1,
t0=t_1.replace(day=1) - timedelta(hours=1))
print(f'MTD return on {t_1.date()} = {return_mtd_1 * 100:.2f} %')
return_ytd_1 = pif.get_share_price_perfomance(t=t_1, t0=t_0)
print(f'YTD return on {t_1.date()} = {return_ytd_1 * 100:.2f} %\n')
#
# after transaction
#
t_2 = datetime(2020, 4, 30)
return_day_2 = pif.get_share_price_perfomance(t=t_2,
t0=t_2 - timedelta(days=1))
print(f'1D return on {t_2.date()} = {return_day_2 * 100:.2f} %')
return_mtd_2 = pif.get_share_price_perfomance(t=t_2,
t0=t_2.replace(day=1) - timedelta(hours=1))
print(f'MTD return on {t_2.date()} = {return_mtd_2 * 100:.2f} %')
return_ytd_2 = pif.get_share_price_perfomance(t=t_2, t0=t_0)
print(f'YTD return on {t_2.date()} = {return_ytd_2 * 100:.2f} %\n')
Result:
1D return on 2020-03-31 = 0.05 %
MTD return on 2020-03-31 = 1.51 %
YTD return on 2020-03-31 = 4.50 %
1D return on 2020-04-30 = 0.05 %
MTD return on 2020-04-30 = 1.44 %
YTD return on 2020-04-30 = 6.01 %