src | ||
.gitignore | ||
.prettierrc | ||
package.json | ||
README.md | ||
test | ||
yarn.lock |
Quick attempt at an optimal portfolio balancer.
Notes:
- The balancing algorithm is not currently optimal (Work in progress). See Closeness section
- No external stock API is used. The given API gives only daily time-series output. This application is built as a stream processing system which would allow automatic rebalancing for specific events such as a price change of more than X%. I may add a randomized price generator as an input.
Install
yarn
To run
Using the pre-seed file with pricing from Jul 12th:
< test node .
Using the pre-seed file and allowing interactive commands:
cat test - | node .
Interactive Commands:
<TICKER>:<price>
Sets the stock price for the given ticker
example:
AAPL:152.3
rebalance:<account_id>
Triggers a rebalance for the given account ID
example:
rebalance:1
note: Account 1 is there by default.
Closeness
The problem of portfolio balancing can be described as such:
Minimize:
w_f - w_t
e.g.
SUM( |c_i*p_i - t_i| ) for stocks i
or
SUM( (c_i*p_i - t_i)^2 ) for stocks i
Subject to:
SUM( c_i*p_i ) <= T
c_i are Integers >= 0 (Except USD)
Where
w_f is the final portfolio
w_t is the target portfolio
c_i is the number of shares of stock i
p_i is the price of stock i
t_i is the total optimal fractional amount invested in stock i
e.g. % allocation * total portfolio value
This problem statement falls in the Mixed Integers Quadratic Programming category. This problem can be solved through enumeration in exponential time 2^n
where n is the total
number of possible stocks purchase (not just ticker), which is very much impractical. Optimisations can be applied to reduce it to 2^n
where n is the total number of tickers,
although still exponential. From there (or possibly through a simplified problem statement), this can possibly be solved in pseudo-polynomial time using algorithms such as
branch-and-bound or Quadknap.
Currently, the algorithm used is non-optimal in some edge-cases but produces very close results in most cases. That algorithm runs in linear time (quasilinear if tickers are not previously sorted) and can be used to evaluate the bounds in a branch-and-bound algorithm for potentially optimal results.
Unfortunately, time constraints prevented me from finishing the implementation as of Sunday July 12th. I will update this README if that changes.
Architecture
The application is built to process a stream of events, such as pricing updates and rebalance triggers. For this reason, no external API was used. As a possibility, I could implement a readable stream which gets initialized from the provided API and then generates random variations of stock prices and a processor that triggers rebalancing on specific events (i.e. APPL went down 5% today) The following message would update the AAPL price to 123.
{type:"pricing", ticker: "AAPL", price: 123}
The following message would trigger an account rebalance on account id 1
{type:"rebalance", accountId: 1}
Although much of the code is simplified for presentation only, storage backends using different databases could easily be added and different stream inputs (such as Kafka, NATS, rabbitMQ) could be implemented. Testing of the codebase would remain easy as dependencies are contained in specific modules with simple interfaces.
Dependencies
This example application currently only depends (ignoreing devDependencies) on the following packages:
- uuid
- strom
UUID is used only to generate an account ID when none is provided. By default an account with id 1
is present for easier user interaction since the goal of this application is
presentation.
Strom is a dependency-free nodejs stream processing utility library, originally forked from https://github.com/Wenzil/Mhysa. Strom was written by Wenzil, Jerry Kurian and Lewis Diamond (myself).