Skip to content

Commit 8ee614c

Browse files
committed
feat: move pd.concat out of for cycle to speed up Rebalancing
1 parent f8bbe79 commit 8ee614c

File tree

1 file changed

+19
-18
lines changed

1 file changed

+19
-18
lines changed

okama/common/helpers/rebalancing.py

Lines changed: 19 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -159,6 +159,9 @@ def wealth_ts(
159159
else: # Calendar rebalancing
160160
rebalancing_by_condition_needed = self.abs_deviation or self.rel_deviation
161161
rebalancing_condition = False
162+
# accumulate chunks and concatenate once after the loop
163+
pw_chunks: list[pd.Series] = []
164+
awi_chunks: list[pd.DataFrame] = [] if calculate_assets_wealth_indexes else None
162165
for n, x in enumerate(ror.resample(rule=self._pandas_frequency, convention="start")):
163166
df = x[1] # select ror part of the grouped data
164167
if n == 0:
@@ -179,27 +182,24 @@ def wealth_ts(
179182
initial_allocation = end_period_weights * end_period_balance
180183
assets_wealth_indexes_local = initial_allocation * (1 + df).cumprod()
181184
if calculate_assets_wealth_indexes:
182-
if assets_wealth_indexes.empty:
183-
assets_wealth_indexes = assets_wealth_indexes_local.copy()
184-
else:
185-
assets_wealth_indexes = pd.concat(
186-
[assets_wealth_indexes_local, assets_wealth_indexes],
187-
verify_integrity=True,
188-
sort=False,
189-
)
185+
# collect asset wealth index chunks for later concatenation
186+
awi_chunks.append(assets_wealth_indexes_local.copy())
190187
wealth_index_local = assets_wealth_indexes_local.sum(axis=1)
191-
if portfolio_wealth_index.empty:
192-
portfolio_wealth_index = wealth_index_local.copy()
193-
else:
194-
portfolio_wealth_index = pd.concat([portfolio_wealth_index, wealth_index_local], sort=False)
195-
end_period_balance = portfolio_wealth_index.iloc[-1]
188+
# collect portfolio wealth index chunks for later concatenation
189+
pw_chunks.append(wealth_index_local.copy())
190+
# use local last value instead of relying on concatenated series
191+
end_period_balance = wealth_index_local.iloc[-1]
196192
if rebalancing_by_condition_needed:
197193
end_period_weights = assets_wealth_indexes_local.iloc[-1].divide(
198194
wealth_index_local.iloc[-1], axis=0
199195
)
200196
rebalancing_condition, condition_abs = self._check_if_rebalancing_required(
201197
assets_wealth_indexes_local, wealth_index_local, target_weights
202198
)
199+
# perform single concatenation outside the loop
200+
portfolio_wealth_index = pd.concat(pw_chunks, sort=False) if pw_chunks else portfolio_wealth_index
201+
if calculate_assets_wealth_indexes and awi_chunks:
202+
assets_wealth_indexes = pd.concat(awi_chunks, verify_integrity=True, sort=False)
203203
# set value for the first date
204204
portfolio_wealth_index.loc[first_wealth_index_date] = initial_inv
205205
portfolio_wealth_index.sort_index(ascending=True, inplace=True)
@@ -218,6 +218,7 @@ def _rebalance_by_condition(
218218
assets_wealth_indexes_local = pd.DataFrame(columns=ror.columns, dtype="float64")
219219
assets_wealth_indexes = pd.DataFrame(columns=ror.columns, dtype="float64")
220220
events_ts = pd.Series(dtype="float64")
221+
awi_rows = [] if calculate_assets_wealth_indexes else None
221222
for n, row in enumerate(ror.itertuples()):
222223
date = row[0]
223224
r = pd.Series(row[1:], index=ror.columns, name=date)
@@ -231,17 +232,17 @@ def _rebalance_by_condition(
231232
assets_wealth_indexes_local *= 1 + r
232233
assets_wealth_indexes_local.rename(date, inplace=True)
233234
if calculate_assets_wealth_indexes:
234-
row = pd.DataFrame(assets_wealth_indexes_local).T
235-
if assets_wealth_indexes.empty:
236-
assets_wealth_indexes = row.copy()
237-
else:
238-
assets_wealth_indexes = pd.concat([assets_wealth_indexes, row])
235+
# collect rows for a single concat after the loop
236+
awi_rows.append(pd.DataFrame(assets_wealth_indexes_local).T)
239237
portfolio_wealth_index_local = assets_wealth_indexes_local.sum()
240238
portfolio_wealth_index[date] = portfolio_wealth_index_local
241239
# Check if rebalancing required
242240
rebalancing_condition, condition_abs = self._check_if_rebalancing_required(
243241
assets_wealth_indexes_local, portfolio_wealth_index_local, target_weights
244242
)
243+
# perform single concatenation outside the loop
244+
if calculate_assets_wealth_indexes and awi_rows:
245+
assets_wealth_indexes = pd.concat(awi_rows, sort=False)
245246
return portfolio_wealth_index, assets_wealth_indexes, events_ts
246247

247248
def _check_if_rebalancing_required(

0 commit comments

Comments
 (0)