some background
Navigating in the fog is difficult, but not impossible. Poland 2020.

Is Financial Independence a product of fortune?

Introduction

This is the second part of the work that attempts to find a recipe towards financial independence - a stage where you no longer need to work to support yourself.

In the previous article, we tried to formulate the problem of personal finance through a system of ordinary differential equations (ODE), which we later solved numerically using python. Given a set of input parameters, our numerical model was able to determine your financial condition - .

In this article, we bring it to the next stage. We add randomness to the equation to account for life’s unpredictability. This time, we want to find out to what degree your financial success is really in your hands?

We will begin this journey by revisiting the math and add some random contributions. Then, we move into simulating some hypothetical scenarios using the so-called Monte Carlo method. Finally, we will use our augmented model to predict your chances with the help of the world’s historical data.

Let’s dive in.

Math revisited

The last model relied on a system two coupled differential equations, which we will reduce to just one for simplicity:

The equation models your total wealth by , which is a time-dependent variable. Its left side is the derivative and the right side is composed of two contributing terms:

  • , which denotes a yearly balance: all income minus all expenses and taxes - simply how much is left after e.g. each year.
  • , which denotes a combined force of investment and inflation that leads to compound interest. Note it’s proportionality to itself, also more on why the terms reside under a logarithm you can find in the earlier article.

Here, the three parameters represent: B

  • - the expected average interest rate on your investment,
  • - the average yearly inflation rate,
  • - a fraction of your wealth you choose to invest, which we’ll call the commitment factor.

Together, the second term can be represented with one number and understood as an effective interest rate. Consequently, whenever , you make money, and with you lose.

The equation itself is a linear first-order differential equation, which can be solved analytically. However, since we will be “randomizing” it later, we will stick to the numerical methods and integrate it using python.

Important remark

We use normalized values for and to make the approach as generic as possible regarding any country, currency, or personal attitude. Therefore, can simply be interpreted as “you managed to save 100% of what you committed to”, which can, for example, translate to three months of a “financial pillow”.

This approach puts a person that earns 100k$ and spends 50k$ equally positioned to another that makes 10k$ and spends 5k$ in terms of financial independence. After all, financial independence is understood as a state under which and the amount that gets generated is positive despite . In other words, the growth at least compensates for the losses, and without a need for active work. Ergo, you can sit and eat forever.

Deterministic solutions

To find a solution, we need to calculate:

which to do numerically is just one function. Here, we arbitrarily set and we model the balance function as

The first interval, we call an active time, as it represents the time, where most of us are actively employed and generating money. The second interval represents the retirement, with expressing balance during this time. To make things more difficult, we let . Thus, with a reasonable pension scheme, we can assume , meaning you can spend 100% of your pension and it won’t affect your total wealth.

/assets/financial-independence-monte-carlo/fig1.png
Figure 1. Deterministic progression of x. Top-left: a sweep of the commitment factor, top-right: varying the initial conditions, bottom-left: effective interest rate sweep, bottom-right: active time sweep. In all cases, the balance function is set to 1 during the active time and -0.5 thereafter.

To test the influence of the different parameters, we show the progression of under a different set of conditions (fig. 1). As we can see, high investment commitment , high expected interest rates , and longer active times result in the solution to be found in the regime where growth generates more growth. Conversely, the initial condition does not affect growth at all - only the absolute amount of .

Success condition

As mentioned earlier, our condition for financial independence is satisfied when while . Another condition we should impose is that this stage is perpetual, meaning that once reached, there is no need to come back to a regular job.

However, once we have accounted for the randomness, the second condition would drive the point into a rather cumbersome analysis of the equation’s stability problem. To avoid that, and to account for a typical life scenario that is earning followed by spending, we will stick to representing the as the following function

meaning that once you pass the retirement age, your yearly balance is expected to be -50% of your prior active time balance. We will, therefore, call it a success if once you get retired (), stays positive, and the derivative of gets positive on average.

Adding life’s randomness

This is an interesting part. The “master” equation offers us a solution for a perfectly deterministic world. Truth is that neither your condition nor (especially) the market’s are. Naturally, we need to account for some randomness in the process, but do it in a way such that our model still makes sense.

For this reason, we will replace the equation’s parameters with parametrized distributions. Then, we will use the so-called Monte Carlo method to find out where it leads us to. Once we collect the outcome through multiple iterations, we can use statistics to discuss the result.

Parametrization

The contributing factors can be divided into human-dependent and market-dependent. The human-dependent factors are and as only these two can be influenced by your decisions. The contrary, , and are more of the world’s condition, which unless you are one of the Masonry, you have no influence on ;)

Mathematically, we can account for randomness to the balance function by injecting gaussian noise in the following way:

Where is defined as earlier, and is a zero-centered Gaussian noise of a specific variance.

Similarly, and can be modeled using a Gaussian noise as:

For beta we have beta

Modeling of is more complicated though. As , we cannot use the normal distribution, but we can replace it with the so-called Beta distribution. The distribution does not only support the constrain but also gives us two parameters (we name them as and to avoid overloading),

which can greatly “paint” human attitude towards investment in general.

Implementation

With this sort of weaponry, we can construct a “life-simulating” class.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
class RealLife:
    def __init__(self):
        self.t0 = 25                        # starting age
        self.beta = np.random.beta(2, 5)    # somewhat natural
        self.mu_r = 0.05                    # investment rate avg
        self.sigma_r = 0.25                 # investment rate std
        self.mu_xi = 0.028                  # inflation avg  
        self.sigma_xi = 0.025               # inflation std
        self.x0 = 0.0                       # initial wealth
        self.balance_fn = self._rect        # balance fn (callable)
        self.sigma_delta = 2.0              # balance fn std
        
        self._rs = []
        self._xis = []
        self._deltas = []

    def live(self, x, t):
        delta = self.sigma_delta * np.random.randn()
        r = self.sigma_r * np.random.randn() + self.mu_r
        xi = self.sigma_xi * np.random.randn() + self.mu_xi
        
        self._rs.append(r)
        self._deltas.append(delta)
        self._xis.append(xi)
        
        rate = self.balance_fn(t - self.t0) \
             + np.log(1 + self.beta * r) * x \
             - np.log(1 + xi) * x
        return rate

    def _rect(self, t, t0, duration=30, floor=-0.5):
        x = np.where(t - t0 <= duration, 1.0, floor)
        mask = np.where(t > t0, 1.0, 0.0)
        return x * mask

Here, all parameters were set to resemble a real-life scenario, but the historical data based results are presented at the end.

It is also important to mention that self.beta is assumed to be time-independent. Consequently, we can interpret it as a situation that once a person is born (an object is instantiated), the investment commitment is chosen and stays the same. As we will use the Monte Carlo method, we should expect enough randomness across the population (after many runs).

Next, we need to integrate the new stochastic differential equation numerically. Since scipy.integrate.odeint performed poorly, we created our routine sdeint that matched the interface of odeint.

1
2
3
4
5
6
7
8
def sdeint(func, x0, t):
    x = np.zeros(t.size, dtype=float)
    x[0] = x0

    for i, dt in enumerate(t[1:]):
        x[i + 1] = x[i] + func(x[i], dt)

    return x

Finally, the simulation code can be wrapped in the following function.

1
2
3
4
5
6
7
8
9
10
11
12
13
import numpy as np
import pandas as pd

def simulate_with_random(you):
    t0 = np.linspace(0, you.t0 - 1, num=you.t0)
    t1 = np.linspace(you.t0, 120, num=(120 - you.t0 + 1))

    x_t0 = np.zeros(t0.size)
    x_t1 = sdeint(you.live, you.x0, t1)

    df0 = pd.DataFrame({'time': t0, 'x': x_t0})
    df1 = pd.DataFrame({'time': t1, 'x': x_t1})
    return pd.concat([df0, df1]).set_index('time')
/assets/financial-independence-monte-carlo/fig2.png
Figure 2. Results of integrating of the randomized equation. Here, the same `sigma` was assigned to both investment rate and inflation rate standard deviation. In all cases, we let it run 50 times.

Monte Carlo

Now, we let it run for 1000 times, increasing the expected . Besides, we randomize the active time to fully randomize the population. Figure 3. shows the results.

/assets/financial-independence-monte-carlo/fig3.png
Figure 3. Monte Carlo simulations of the fully randomized equation. The balance function's active time is set to a random integer number from 15 to 40 years.

As we can see, whenever it is virtually impossible to get into financial independence. This is because and after integrating the solution experiences an exponential decay and the equation remains stable. However, increasing does not guarantee success either, due to the influence of the active time and the commitment factor . Even for an artificially high , a large fraction of runs fails due to the reasons mentioned above.

Fortunately, both and are factors that depend on everyone’s personal choices. Therefore, if we freeze these to while letting and remain random, we should be able to estimate your chances of success at least to a certain extent.

Your chances against the historical data

To get a feeling for what it is like to combat the market, we need to plug in some reasonable values of and . Assuming that “the history repeats itself”, we can argue that we can simulate the future using historical data. To get the values for inflation, we use the historical (1914-2020) data for the US from here. Similarly, to estimate the market’s behavior in terms of the mean and the variance, we use the S&P 500 data from 1928-2020.

According to the data, we have and .

Next, we sweep and , while using the values we just computed.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
from itertools import product


t = np.linspace(0, 120, num=121)
ACTIVE_TIME = np.linspace(10, 40, num=31)
BETAS = np.linspace(0.0, 1.0, num=11)
np.random.seed(42)

diags = []
for wy, beta in product(ACTIVE_TIME, BETAS):
    for _ in range(1000):
        you = RealLife()
        you.mu_r = 0.01 * mu_r          # S&P500 mean
        you.sigma_r = 0.01 * sigma_r    # S&P500 std
        you.mu_xi = 0.01 * mu_xi        # inflation mean
        you.sigma_xi = 0.01 * sigma_xi  # inflation std
        you.beta = beta                 
        you.balance_fn = lambda t: rect(t, you.x0, wy)
        df = simulate_with_random(you)

        passive = int(float(df[df.index > you.t0 + du] \
                    .diff().sum()) > 0)

        diags.append({
            'active': wy,
            'beta': you.beta,
            'avg_r': you.get_average_r(),
            'avg_delta': you.get_average_delta(),
            'avg_xi': you.get_average_xi(),
            'passive': passive,
        })

df = pd.DataFrame(diags)
df_agg = df[['active', 'beta', 'passive']] \
    .groupby(by=['active', 'beta'] \
    .mean() \
    .unstack() * 100

Final result

/assets/financial-independence-monte-carlo/fig4.png
Figure 4. Final result of the 1000 runs for each (T, beta)-pair with historical market data. The results were expressed in %.

Figure 4. shows the final result of the Monte Carlo simulation for each -pair. As we can see, any practically exclude you from ever becoming financially independent. Truth is that if you dream of securing a bright future for yourself, you need to learn to invest - whatever that means to you. This can mean anything from being a regular trader to a skilled entreprenour - you need to learn to multiply what you have.

The second most important factor is your active time. This one, however, should be seen more as an “opportunity” window during which you can accumulate wealth. The longer it is, the higher the chances you will gain enough of the “fuel” to get your financial ship to fly.

However, even with 100% commitment and 40 years of hardship, the odds seem to play against you! Fortunately, you should remember that we used an oversimplified model for the function. In reality, if you manage to translate what you learned into a higher salary and stay away from the temptation to overspend, your wealth will accumulate faster. Furthermore, nobody said you will be left to market “caprice”. Unless your only investment strategy is to safely allocate your money to some fund, you will probably learn to navigate and sail even during storms.

With that in mind, please find these results only as a way to encourage you to try, as they are not the ultimate recipe, but rather an attempt to figure out what matters. Good luck!

PS. If you want to play with the code and simulate for yourself, you can find the code on Github.