All projects

Coursework · Jun 2024

Portfolio Allocation: Best Within the Model

A fun exploration of backtracking, Indonesian stocks, and why an optimal answer is only as meaningful as the assumptions behind it.

My contribution

Solo research, modeling, implementation, and experimentation

Portfolio Allocation: Best Within the Model screenshot 1
1 / 3

A fun exploration of algorithms and stocks

This project was a fun exploration project. For my solo IF2211 Algorithm Strategies paper, I could choose any problem and algorithmic approach I wanted. At the time I was diving deeper into economics, especially stocks, so portfolio allocation gave me a way to connect that interest with backtracking.

The primary goal was not to build an investment product people should follow. I wanted to demonstrate how backtracking could search for the best answer under a collection of financial constraints.

To optimize a portfolio, I needed some definition of return. Building a real forecasting model would have become a separate project, so I made a deliberate assumption: use twenty years of historical compound annual growth as the expected return for the next year.

That choice made the problem computable, but not necessarily realistic. It replaced an unknown future with a measurable historical value, allowing the paper to focus on the search algorithm while keeping the limitation visible.

CAGR=(VendVstart)1/20−1\mathrm{CAGR} = \left(\frac{V_{\text{end}}}{V_{\text{start}}}\right)^{1/20} - 1
Twenty years of historical price growth became the simplified return estimate for each stock.

Diversification made allocation meaningful

Without additional constraints, the answer would barely be a portfolio. The algorithm could simply place all available money into the stock with the highest historical return.

I limited the portfolio to one stock per industry, capped the allocation to each stock, and prevented a purchase from exceeding roughly one percent of its market capitalization. The diversification rule gave backtracking a meaningful combination problem, while the liquidity limit came from reading about the Indonesian market and from my own experience with how difficult large positions can be to trade.

I chose backtracking because I wanted the result to be the best portfolio that complied with the constraints, not merely the first attractive answer produced by a greedy choice. The search explored stock combinations, rejected candidates that broke a constraint, and restored its state as it returned from each recursive branch.

That guarantee had an important boundary. “Best” only meant best according to my historical-return objective and simplified constraints. The algorithm could solve the model exactly without proving that the model described good investing.

maximize∑ixi⋅100 pi⋅(1+gi)\text{maximize} \quad \sum_{i} x_{i} \cdot 100\,p_{i} \cdot \left(1 + g_{i}\right)
The search maximizes the modeled one-year value of the selected stock lots under the budget, industry, allocation, and liquidity constraints.

Ninety-eight stocks were still a large search space

I narrowed the Indonesian stock universe to 98 candidates so clearly unsuitable stocks would not enter the search. Even then, I knew the combinations could grow quickly. The difficult part of the project became improving performance without losing the exhaustive character I wanted.

Constraint checks eliminated branches that could not afford a stock, repeated an industry, or exceeded an allocation limit. A full Rp500 million experiment still took around 43 seconds, which did not surprise me. Exponential exploration was expensive, but that cost was part of what I wanted to investigate.

I first implemented the idea in Python, then reproduced it in C++ because the search was too slow. I also experimented with placing the algorithm in another thread because I was considering parallel execution.

The program immediately waited for that thread, so it did not create useful parallelism in the final version. That experiment reminded me that concurrency by itself does not reduce a search space. The structure of the algorithm matters more than merely giving the same work another thread.

A correct algorithm still needs a good model

When the program finally produced a complete portfolio, that was the most satisfying moment. One experiment returned seven stocks and a modeled one-year gain above 100 percent.

I treated that number as a mathematical consequence of the historical CAGR assumptions, not as a realistic forecast. The result demonstrated that the implementation worked, while its implausibility also exposed how much the objective depended on the model behind it.

If I rebuilt this today, I would use a different optimization approach and model financial considerations such as risk, volatility, and correlation more carefully. Those factors would need their own heuristic quantification instead of being hidden behind historical return alone.

The lasting lesson is that an algorithm cannot solve a problem the model fails to express. Backtracking can find the best answer inside the world I define, but human judgment still decides whether that world contains the right assumptions.

What stayed with me

  • The best answer is only best inside its assumptions.
  • Diversification turned a ranking into an allocation problem.
  • Another thread does not make the search space smaller.
  • A correct algorithm still needs a good model.

Run it locally

Clone the source repository or download the project ZIP, then run these commands from the project folder.

Explore the complete project

Download the source, assets, and included documentation as a ZIP archive.

Download project ZIP
bash

Build and run

$ g++ -O2 -std=c++17 -o portfolio main.cpp
$ ./portfolio

# prompts for the per-stock cap (e.g. 30) and the budget (e.g. 500000000).
# Run from the repository root — the CSV path is relative.
# The 30% / Rp 500,000,000 case takes about a minute.