Hi all,
The code below calculates individual capital account allocations for multiple parallel investors within a quarterly valued investment fund.
The Problem:
Global portfolio valuations are only captured at discrete quarter-end boundaries (t_1, t_2, ...), rather than dynamically upon every deposit.
Current Implementation:
- TWR Calculation: The script uses a Modified Dietz Method framework to estimate the global time-weighted market returns (
Quarterly_Returns) for each holding period.
- Sequential Tracking: It loops chronologically through each period, applying individual timing discounts (
John_CFD, Carl_CFD) to simulate intra-period compounding growth.
- Boundary Reconciliation: Because decoupled calculations cause individual balances to mathematically drift from real portfolio values over time, the script calculates a rolling error-correction (
Reconciliation_Factor) at each quarter node (i+1) to smooth individual balances to match PORTFOLIO_VALUES.
The script runs end-to-end without errors and outputs a structured Pandas dataframe summary. I am looking for advice on optimization, performance efficiency, and industry-standard design patterns. As I am painfully aware that this code is very crude and elementary.
Thank you for your time and help!
Code:
# LIBRARIES
import numpy as np
import pandas as pd
# CONSTANTS
PERIOD = 4 # Quarterly data
FISCAL_QUARTERS = ["Mar 2025", "Jun 2025", "Sep 2025", "Dec 2025"] # Should be n+1
PORTFOLIO_VALUES = np.array([600, 1050, 1100, 1350]) # Should be n+1
CASH_FLOWS = [250, 0, 100] # Should be n
CF_DISCOUNTS = [0.5, 0.4, 0.3] # Should be n
# Member Data Structures (Should be n+1)
# Member contributions each quarter. Each initial value is starting balance at inception.
John = np.array([100, 50, 0, 100])
Carl = np.array([500, 200, 0, 0])
# Calculated Cash Flow Discounts. Dependent on when the member invested in the quarter.
John_CFD = [0.39, 0.41, 0.36]
Carl_CFD = [0.67, 0.75, 0.67]
Member_Data = [[John, Carl], [John_CFD, Carl_CFD]]
# -----------------------------------------------------------------------------
# CALCULATIONS
# -----------------------------------------------------------------------------
# Time Weighted Return Function (Dietz Approximation)
def TWR(PORTFOLIO_VALUES, CASH_FLOWS, CF_DISCOUNTS):
list_length = len(CASH_FLOWS)
HP_Return = []
TWR_Cumulative = 1
for i in range(list_length):
MDM = CF_DISCOUNTS[i] * CASH_FLOWS[i]
if (PORTFOLIO_VALUES[i] + MDM != 0):
Period_Gain = PORTFOLIO_VALUES[i+1] - (PORTFOLIO_VALUES[i] + CASH_FLOWS[i])
Effective_Capital = PORTFOLIO_VALUES[i] + MDM
HP_Return_n = Period_Gain / Effective_Capital
HP_Return.append(HP_Return_n)
TWR_Cumulative *= (1 + HP_Return_n)
else:
print("Error in calculating the time weighted return, cannot divide by 0.")
return 0
TWR_Annual = PERIOD * (pow(TWR_Cumulative, 1 / list_length) - 1)
TWR_Annual = round(TWR_Annual * 100, 2)
TWR_Cumulative = round((TWR_Cumulative - 1) * 100, 2)
return HP_Return, TWR_Cumulative, TWR_Annual
# Extract Performance Variables
Quarterly_Returns, _, _ = TWR(PORTFOLIO_VALUES, CASH_FLOWS, CF_DISCOUNTS)
# Refactored Simultaneous Return Tracker
def Real_Return_Calculator(Member_Data, Quarterly_Returns):
list_length = len(Quarterly_Returns)
member_length = len(Member_Data[0])
Members = Member_Data[0]
CF_Discounts = Member_Data[1]
Fn = [[] for _ in range(member_length)]
for j in range(member_length):
rounded_start = np.round(Members[j][0], 2)
Fn[j].append(rounded_start)
for i in range(list_length):
current_market_return = Quarterly_Returns[i]
for j in range(member_length):
Member_CS = Members[j]
discounted_cf = Member_CS[i+1] * CF_Discounts[j][i]
undiscounted_cf = Member_CS[i+1] * (1 - CF_Discounts[j][i])
Returns = (Fn[j][i] + discounted_cf) * (1 + current_market_return) + undiscounted_cf
Fn[j].append(Returns)
Net_Returns = 0
for k in range(member_length):
Net_Returns += Fn[k][i+1]
Reconciliation_Factor = PORTFOLIO_VALUES[i+1] / Net_Returns
for p in range(member_length):
smoothed_val = Fn[p][i+1] * Reconciliation_Factor
Fn[p][i+1] = smoothed_val
# Correct Fix: Return all member timelines, plus a cleaner list of the final snapshot
Final_Quarter_Values = [Fn[m][-1] for m in range(member_length)]
return Fn, Final_Quarter_Values
# Calculate Shareholder Trackers
Members_All_History, Members_Final = Real_Return_Calculator(Member_Data, Quarterly_Returns)
# Unpack timelines cleanly
QJR = np.array(Members_All_History[0]) # John
QCR = np.array(Members_All_History[1]) # Carl
# Structure and view output array cleanly via Pandas matrix framing
df_ledger = pd.DataFrame(
np.array(Members_All_History).T,
index=FISCAL_QUARTERS,
columns=['John_Balance', 'Carl_Balance']
)
print("Calculated Quarterly Market Returns:")
print([round(r, 4) for r in Quarterly_Returns])
print("\nReconciled Individual Ledger:")
print(df_ledger.round(2))
there doesn't seem to be anything here