8
$\begingroup$

The Problem

I have two compound interest accounts, Account A, which has 0.5% yearly interest, but I have to keep \$30,000 there to unlock some bonus; and Account B, which has 2% yearly interest. Transferring between the two accounts costs \$0.5. I only want to keep \$30,000 in Account A to have the bonus and I want to transfer the rest to Account B. No additional money comes to Account A. Both accounts pay interest every minute.

I want to find the optimal times of moving funds from Account A to Account B that results in the most profit (a finite time horizon is imposed). The frequency can vary as time progresses. Here is a similar problem which has a solution that outputs the optimal times a transfer should happen.

What I've Tried

I've tried solving it using the compound interest formula, but got stuck at the point where the money gets transferred from one account to another, because calculating $\text{secondAmountAndInterest}$ yields incorrect results. I tried to make the calculation so it's transferring the money from one account into another (that's why I subtracted $B$ from $\text{firstAmountAndInterest}$ when calculating $\text{secondAmountAndInterest}$). Here's what I've tried:

Initial balance in USD: $B$ = 30000
First APR: $A_1 = 0.005$
Second APR: $A_2 = 0.02$
Transfer fee in USD: $T = 0.5$

\begin{align} \text{firstAmountAndInterest} & = B \times \left(\frac {1+A_1} x\right) ^ x \\ \text{secondAmountAndInterest} & = (\text{firstAmountAndInterest} - B) \times \left(\frac {1+A_2} x\right) ^ x \\ y & = B-\text{secondAmountAndInterest} - x \times T \end{align}

Here's a Python script I wrote that calculates the formula's maximum:

import scipy
from scipy import optimize


def f(x):
    balance_in_usd = 30000
    first_apr = 0.005
    second_apr = 0.02
    transfer_fee_in_usd = 0.5

    if x == 0:
        return 0

    first_amount_and_interest = balance_in_usd * (1 + first_apr / x) ** x
    second_amount_and_interest = (first_amount_and_interest - balance_in_usd) * (1 + second_apr / x) ** x

    return balance_in_usd + second_amount_and_interest - x * transfer_fee_in_usd


max_x = scipy.optimize.fmin(lambda x: -f(x), 0)

print("Frequency or no. of times you have to move funds annually:", max_x[0])
print("After how many days you have to move funds:", 365.25 / max_x[0])
print("After how many years you have to move funds:", 1 / max_x[0])

Output:

Optimization terminated successfully.
         Current function value: -30152.504308
         Iterations: 25
         Function evaluations: 50
Frequency or no. of times you have to move funds annually: 0.9070000000000009
After how many days you have to move funds: 402.70121278941525
After how many years you have to move funds: 1.1025358324145524

I've successfully calculated how often to compound fees when there is only one account, here's that as well. Somehow, I need to modify this so that it handles Account B as well.

Initial balance in USD: $B$ = 30000
First APR: $A_1 = 0.005$
Transfer fee in USD: $T = 0.5$

$$ y = B \times \left(\frac {1 + A_1} x\right)^x - x \times T $$

$\endgroup$
13
  • $\begingroup$ How do you know the results are incorrect? $\endgroup$
    – Babu
    Commented Aug 7, 2022 at 12:35
  • 1
    $\begingroup$ @ArcticChar The full amount above the initial balance (so the total amount in Account A minus the initial amount, \$30,000) $\endgroup$ Commented Aug 7, 2022 at 13:42
  • 3
    $\begingroup$ It is typical in problems of this kind that a finite time horizon is imposed. While a careful Reader might be able to infer what you mean from your code, such an important point should be articulated in your Question's setup. $\endgroup$
    – hardmath
    Commented Aug 7, 2022 at 14:38
  • 2
    $\begingroup$ It is clear that without a time limit/endpoint, the "profit" (account balances) will increase without limit (yes, going to infinity) using the passive strategy of no transfers. So to define a problem with an optimal profit, you need to impose a bound on time. Perhaps you mean this when you say "it doesn't go to infinity." My point is that to formulate your math problem, that time limit needs to be specified, at least by abstract variable name. $\endgroup$
    – hardmath
    Commented Aug 7, 2022 at 19:20
  • 1
    $\begingroup$ Okay, I'll write something up for you. However the gist of the situation is that we cannot treat the "frequency" $x$ as a continuous variable to be optimized. The situation is more complicated than that, which is why I think it deserves some thought from a different perspective. $\endgroup$
    – hardmath
    Commented Aug 9, 2022 at 16:36

2 Answers 2

3
+50
$\begingroup$

With interest being paid "every minute" it would be accurate to use the continuous compounding model. Let's do some basic calculations for the case in which the time horizon is one year, and we make no transfers.

Continuous compounding says that the $30,000 principal in account A will become:

$$ 30000 \exp(0.005) = 30150.375625782 $$

while a more literal minute-by-minute compounding gives:

$$ 30000 \left(1 + \frac{0.005}{525600}\right)^{525600} = 30150.375624331 $$

This difference is tiny relative to the $0.50 cost of transfers, so we accept the continuous compounding formula for the sake of simplicity.

This computation also shows that at most 300 transfers from account A to account B could be done (in the one-year time interval), neglecting the possibility to use funds accruing in account B for such costs (they would certainly be counterproductive to maximizing profit).

This takes care of the case of not doing any transfers. The number of transfers is discrete, a value between $0$ and $300$. So instead of trying to model the "frequency" of transfers as a real number, we should explore the effect of doing a smallish number of transfers. The timing of transfers need not be periodic.

Let's begin with the case of one transfer at time $t_1$ during the year interval. The earliest we could make $t_1$ would be the point at which an accrued interest in account A reaches the transfer cost. That's clearly not optimal as nothing will actually be deposited in account B, so the profit will be less than leaving all the funds in account A until the end of the year, at which point the transfer if done at $t_1 = 1$ simply reduces the profit calculated above for no transfers by the transfer cost.

So we expect the optimal value of $t_1$ will occur between those extremes. An expression can now be given:

$$ \left[30000 \left(\exp(0.005 t_1)-1 \right) - 0.5 \right] \exp(0.02(1-t_1)) + 30000 \left(\exp(0.005 (1-t_1)) \right) $$

for the monies transferred from account A to account B at time $t_1$, there accruing interest at the higher rate for the remaining time $1-t_1$, plus the monies left in account A that accrue the lower rate of interest over that time. This expression includes the original deposit of $30,000 as is consistent with the earlier calculations without transfers.

I used a spreadsheet to isolate which $t_1$ maximizes the profit, and within the limited precision of that, the best value of $t_1$ is very near $0.5$. It gives an improvement (over no transfer) to $30150.43734 (so about a six cent increase).

Plan of Attack

I suspect the one transfer case, suitably generalized, admits a closed form solution, and this may allow us to solve multiple transfer problems by a dynamic programming approach. So we begin by polishing the one transfer case.

$\endgroup$
6
  • 1
    $\begingroup$ Thank you for your explanation! One question: how can I retreive a list of optimal transfer times from this formula? $\endgroup$ Commented Aug 10, 2022 at 17:33
  • $\begingroup$ Note that I fixed a mistake in the expression, omitting a crucial $-1$ in the first part of the calculation. Evaluating the expression for $t_1$ in the middle of the interval shows that one transfer can improve on the no transfer case. $\endgroup$
    – hardmath
    Commented Aug 11, 2022 at 0:06
  • $\begingroup$ How can I include more transfers in the formula instead of just $t_1$? $\endgroup$ Commented Aug 11, 2022 at 0:54
  • $\begingroup$ More transfers require more terms (to sum), similar to what happened in comparing no transfers to one transfer. I've got a little polishing to do on the exact maximum of the one transfer case, and then I'll work on the two transfer case. $\endgroup$
    – hardmath
    Commented Aug 11, 2022 at 2:44
  • $\begingroup$ That's dedication, much appreciate your help! $\endgroup$ Commented Aug 11, 2022 at 3:00
1
+100
$\begingroup$

Account A at each transfer $b_k$ to account B follows the recurrence

$$ \cases{\alpha_0 = A_1 \\ \alpha_k = \alpha_{k-1}e^{r_1\delta_k}-b_k} $$

Account B grows also according to the recurrence

$$ \cases{\beta_0 = A_0e^{r_2\delta_1} \\ \beta_k = (\beta_{k-1} + b_k-\lambda)e^{r_2\delta_{k+1}}} $$

Here $\delta_k = t_k-t_{k-1}$. The restrictions are

$$ \mathcal{R}=\cases{b_k\ge \lambda \\ \delta_k\ge 0 \\ \alpha_k \ge A_1 \\ \sum_k\delta_k = t_{max}} $$

Taking $k_{max}$ transfers we have the optimization setup:

$$ \max_{b,\delta}(\alpha_{k_{max}}+\beta_{k_{max}-1})\ \ \ \text{s. t.}\ \ \ \mathcal{R} $$

Follows a MATHEMATICA script which implements this algorithm.

tmax = 0.5;
data = {lambda -> 0.5, A1 -> 30000, r1 -> 0.005, r2 -> 0.02, A0 -> 0}
alpha[0] = A1;
beta[0] = A0 Exp[r2 dt[1]];
vars = {};
restrs = {};
kmax = 10;

For[k = 1, k <= kmax, k++,
 If[k < kmax, alpha[k] = alpha[k - 1] Exp[r1 dt[k]] - b[k], alpha[k] = alpha[k - 1] Exp[r1 dt[k]]]; 
 beta[k] = (beta[k - 1] + b[k] - lambda) Exp[r2 dt[k + 1]]; 
 If[k < kmax, AppendTo[vars, {b[k], dt[k]}]]; 
 If[k < kmax, AppendTo[restrs, alpha[k] - A1]]
 ]

restrs1 = restrs;
AppendTo[restrs, Table[b[k] - lambda, {k, 1, kmax - 1}]];
AppendTo[restrs, Table[dt[k], {k, 1, kmax}]];
restrs0 = Thread[Flatten[restrs] > 0];
AppendTo[restrs0, tmax - Sum[dt[k], {k, 1, kmax}] == 0];
restrs0 = restrs0 /. data;
vars = Join[Table[b[k], {k, 1, kmax - 1}], Table[dt[k], {k, 1, kmax}]];
obj = alpha[kmax] + beta[kmax - 1] /. data;
sol = NMaximize[Join[{obj}, restrs0], vars, Method -> "DifferentialEvolution"]

According to the outcomes, for big $k_{max}$, a possible solution could give near optimal results with $\delta_k=\frac{t_{max}}{k_{max}}$: in this case, the recurrences for $\alpha_k,\beta_k$ have closed form.

NOTE

Considering $\delta_k = \frac{t_{max}}{k_{max}},\eta_1 = e^{r_1\frac{t_{max}}{k_{max}}},\eta_2 = e^{r_2\frac{t_{max}}{k_{max}}}$ the recurrences read

$$ \cases{\alpha_0 = A_1 \\ \alpha_k = \alpha_{k-1}\eta_1-b_k} $$

$$ \cases{\beta_0 = A_0\eta_2 \\ \beta_k = (\beta_{k-1} + b_k-\lambda)\eta_2} $$

and the optimization problem reads:

$$ \max_{b}(\alpha_{k_{max}}+\beta_{k_{max}-1})\ \ \ \text{s. t.}\ \ \ \mathcal{R}_0 $$

with

$$ \mathcal{R}_0=\cases{b_k \le A_1\\ b_k\ge \lambda \\ \alpha_k \ge A_1 } $$

which can be solved with a simplex (Linear Programming) approach.

Follows an attachable script which handles the Linear Programming issue.

obj0 = alpha[kmax] + beta[kmax - 1] /. Thread[Table[dt[k], {k, 1, kmax}] -> tmax/kmax] /. data;
varsb = Table[b[k], {k, 1, kmax - 1}];
coefs = Grad[obj0, varsb];
c0 = obj0 /. Thread[varsb -> 0];
AppendTo[restrs1, (beta[kmax] - A1) /. {b[kmax] -> 0}];
restrs10 = restrs1 /. Thread[Table[dt[k], {k, 1, kmax}] -> tmax/kmax] /. data;
nl = Length[restrs10];
A = {};
AB = {};
zerosb = Thread[varsb -> 0];

For[k = 1, k < nl, k++,
 AppendTo[A, Grad[restrs10[[k]], varsb]];
 AppendTo[AB, restrs10[[k]] /. zerosb]
 ]

Ab = IdentityMatrix[kmax - 1];
Bb = Table[lambda, {k, 1, kmax - 1}] /. data;
Ac = -Ab;
Bc = Table[-A1, {k, 1, kmax - 1}] /. data;
AA = Join[A, Ab];
AA = Join[AA, Ac];
BB = Join[-AB, Bb];
BB = Join[BB, Bc];

x = LinearProgramming[-coefs, AA, BB]
coefs.x + c0
$\endgroup$
9
  • $\begingroup$ Thank you for the answer! Unfortunately, when I edit kmax to be 300, the script doesn't run and throws an error. Seemingly, it times out. $\endgroup$ Commented Aug 17, 2022 at 9:33
  • $\begingroup$ When $k_{max}$ is big, a good answer can be obtained by using the hypothesis $\delta_k = \frac{t_{max}}{k_{max}}, b_k = b^*, $ and solving then the recurrences with closed form. $\endgroup$
    – Cesareo
    Commented Aug 17, 2022 at 9:53
  • $\begingroup$ How would that look like in code? $\endgroup$ Commented Aug 17, 2022 at 11:25
  • $\begingroup$ Please. See attached NOTE. $\endgroup$
    – Cesareo
    Commented Aug 17, 2022 at 11:46
  • $\begingroup$ Thank you! I meant how would that look like in Mathematica. $\endgroup$ Commented Aug 17, 2022 at 15:03

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .