Welcome to the Intrinsic Value Calculation Service project! This project provides a comprehensive and reliable method to calculate the intrinsic value of a stock based on fundamental analysis. The intrinsic value is an essential concept for investors to determine whether a stock is overvalued or undervalued.
Investing in stocks requires a thorough understanding of a company's financial health and future growth prospects. One way to evaluate a stock's worth is through intrinsic value calculation, which estimates the inherent worth of a stock based on its expected future cash flows.
The intrinsic value calculation helps investors make informed decisions about buying or selling a stock by comparing the intrinsic value with the current market price. If the intrinsic value is higher than the current market price, the stock is considered undervalued and may be a good investment opportunity. Conversely, if the intrinsic value is lower than the current market price, the stock is considered overvalued.
This project provides a Spring Boot microservice that calculates the intrinsic value of a stock using the following parameters:
- Free Cash Flow for the last year (fcfLastYear)
- Expected growth rate for the next 10 years (growthRate)
- Discount rate (discountRate)
- Terminal growth rate (terminalGrowthRate)
- Total number of outstanding shares (sharesOutstanding)
- Net debt (netDebt)
- Current market price of the stock (currentMarketPrice)
The service exposes a REST API to receive these inputs and returns the intrinsic value per share along with a remark indicating if the stock is undervalued or overvalued.
Free Cash Flow represents the cash generated by the company that is available for distribution to its securities holders. It is a key indicator of a company's financial health. The intrinsic value calculation starts with the FCF of the last year.
The growth rate is the expected rate at which the company's FCF will grow over the next 10 years. A higher growth rate indicates expectations of higher future cash flows.
The discount rate is used to discount future cash flows to their present value. It reflects the time value of money and the risk associated with the investment. A higher discount rate indicates higher perceived risk.
The terminal growth rate is used to estimate the value of the company's cash flows beyond the forecast period. It reflects long-term growth expectations.
Shares outstanding refer to the total number of shares issued by the company that are held by investors, including restricted shares owned by the company's officers and insiders.
Net debt is the company's total debt minus its cash and cash equivalents. It is used to adjust the enterprise value to account for the company's financial obligations.
The current market price is the price at which the stock is currently trading in the market. It is used to compare against the intrinsic value to determine if the stock is undervalued or overvalued.
Future cash flows are projected by growing the last year's FCF (fcfLastYear) by the growth rate (growthRate) each year for the next 10 years.
FCF_year = FCF_last_year * (1 + growthRate)
BigDecimal[] forecastedFcfs = new BigDecimal[forecastPeriodYears];
forecastedFcfs[0] = dto.getFcfLastYear().multiply(BigDecimal.ONE.add(dto.getGrowthRate()));
for (int year = 1; year < forecastPeriodYears; year++) {
forecastedFcfs[year] = forecastedFcfs[year - 1].multiply(BigDecimal.ONE.add(dto.getGrowthRate()));
}
The terminal value is estimated using the terminal growth rate (terminalGrowthRate) and discount rate (discountRate). If the discount rate is zero, the terminal value is calculated differently to avoid division by zero.
Terminal Value = FCF_year10 * (1 + terminalGrowthRate) / (discountRate - terminalGrowthRate)
BigDecimal terminalValue;
if (dto.getDiscountRate().compareTo(BigDecimal.ZERO) > 0) {
terminalValue = forecastedFcfs[forecastPeriodYears - 1].multiply(BigDecimal.ONE.add(dto.getTerminalGrowthRate()))
.divide(dto.getDiscountRate().subtract(dto.getTerminalGrowthRate()), RoundingMode.HALF_UP);
} else {
terminalValue = forecastedFcfs[forecastPeriodYears - 1].multiply(BigDecimal.ONE.add(dto.getTerminalGrowthRate()));
}
Future cash flows and terminal value are discounted back to their present value using the discount rate (discountRate).
Present Value of FCFs = Sum(FCF_t / (1 + discountRate)^t) for t = 1 to 10
BigDecimal totalPresentValueOfFcfs = BigDecimal.ZERO;
for (int year = 0; year < forecastPeriodYears; year++) {
totalPresentValueOfFcfs = totalPresentValueOfFcfs.add(forecastedFcfs[year].divide(BigDecimal.ONE.add(dto.getDiscountRate()).pow(year + 1), RoundingMode.HALF_UP));
}
BigDecimal presentValueOfTerminalValue = terminalValue.divide(BigDecimal.ONE.add(dto.getDiscountRate()).pow(forecastPeriodYears), RoundingMode.HALF_UP);
The total enterprise value is the sum of the present values of all future cash flows and the terminal value.
Total Enterprise Value = Present Value of FCFs + Present Value of Terminal Value
BigDecimal totalEnterpriseValue = totalPresentValueOfFcfs.add(presentValueOfTerminalValue);
The adjusted enterprise value is the total enterprise value adjusted for net debt (netDebt) to account for the company's financial obligations.
Adjusted Enterprise Value = Total Enterprise Value - netDebt
BigDecimal adjustedTotalEnterpriseValue = totalEnterpriseValue.subtract(dto.getNetDebt());
The intrinsic value per share is the adjusted enterprise value divided by the number of outstanding shares (sharesOutstanding).
Intrinsic Value per Share = Adjusted Enterprise Value / sharesOutstanding
BigDecimal intrinsicValuePerShare = adjustedTotalEnterpriseValue.multiply(new BigDecimal(1000)).divide(dto.getSharesOutstanding(), RoundingMode.HALF_UP);
The intrinsic value per share is compared with the current market price (currentMarketPrice) to determine if the stock is undervalued or overvalued.
- If Intrinsic Value per Share > Current Market Price, the stock is Undervalued.
- If Intrinsic Value per Share < Current Market Price, the stock is Overvalued.
To calculate the intrinsic value of a stock, send a POST request to /api/v1/intrinsic-value/calculate
with the following JSON payload:
{
"fcfLastYear": 1.1,
"growthRate": 0.15,
"discountRate": 0.10,
"terminalGrowthRate": 0.03,
"sharesOutstanding": 122,
"netDebt": 0.5,
"currentMarketPrice": 291.06
}
The response will contain the intrinsic value per share and a remark indicating if the stock is undervalued or overvalued:
{
"intrinsicValue": 318.91,
"currency": "USD",
"remarks": "Undervalued"
}
The project includes comprehensive unit tests to validate the intrinsic value calculation for various scenarios. Each test case is designed to verify a specific aspect of the calculation to ensure robustness and accuracy.
-
calculateIntrinsicValue_shouldReturnCorrectIntrinsicValueAndRemark_whenInputIsValid: Verifies that the intrinsic value calculation returns the correct value and remark for valid input data. This test ensures that the calculation logic is correct under normal circumstances.
-
calculateIntrinsicValue_shouldReturnBelowZeroIntrinsicValue_whenFCFIsZero: Checks if the intrinsic value calculation returns a value below zero when the Free Cash Flow (FCF) is zero. This test ensures that the logic correctly handles cases where the company generates no free cash flow.
-
calculateIntrinsicValue_shouldReturnHigherIntrinsicValue_whenNetDebtIsNegative: Ensures that the intrinsic value calculation returns a higher value when the net debt is negative. Negative net debt indicates that the company has more cash than debt, increasing its intrinsic value.
-
calculateIntrinsicValue_shouldReturnLowerIntrinsicValue_whenGrowthRateIsZero: Verifies that the intrinsic value calculation returns a lower value when the growth rate is zero. This test ensures that the calculation correctly reflects the lack of growth in future cash flows.
-
calculateIntrinsicValue_shouldReturnLowerIntrinsicValue_whenTerminalGrowthRateIsZero: Checks if the intrinsic value calculation returns a lower value when the terminal growth rate is zero. This test ensures that the logic correctly handles cases where there is no growth expected beyond the forecast period.
-
calculateIntrinsicValue_shouldHandleLowGrowthRateAndHighDiscountRate: Ensures that the intrinsic value calculation handles scenarios with low growth rate and high discount rate correctly. This test verifies that the calculation reflects higher perceived risk and lower growth expectations.
-
calculateIntrinsicValue_shouldHandleHighGrowthRateAndLowDiscountRate: Verifies that the intrinsic value calculation handles scenarios with high growth rate and low discount rate correctly. This test ensures that the calculation reflects lower perceived risk and higher growth expectations.