More Projects
59 in totalJPMorgan Software Engineering Simulation
Virtual experience — four functions against a supplied codebase · 2024
A Forage virtual-experience exercise, not client work: JPMorgan publishes starter repos with the interesting parts stubbed out and you fill them in. All three tasks completed against a provided codebase — the market simulator, the React scaffold, the Perspective wiring and the test harness all arrive intact. What the tasks add is a price-feed client that derives mid prices and their ratio from the supplied server, its unit tests, the switch from a single fetch to a streaming interval, and a rebuilt Perspective schema that computes the bid-ask ratio with fixed bounds and emits an alert only on breakout.
Project Details
Virtual experience — four functions against a supplied codebase
2024
Coursework · Data
A Forage virtual experience programme, not client work. JPMorgan Chase publishes starter repos with the interesting parts stubbed out and you fill them in. Three tasks, all completed.
Being clear about the split matters, because most of what is in these three repositories was written by JPMorgan. The market simulator, the React scaffold, the Perspective wiring and the test harness all arrive intact; what is missing is a handful of functions and a handful of configuration lines, each marked with a comment telling you to update it. What the exercise teaches is not how to build the system but how to read one you did not write and find the four places that matter.
The simulator you are handed
server3.py is the most substantial file in the set and none of it is yours to write, which
is why it is worth understanding. It is a self-contained market: a bounded random walk
generates a price series, an order generator turns each tick into bids and asks around it,
and an order book accumulates them with a per-order age so stale levels expire. Orders clear
against each other through a comparison operator passed in as an argument, so the same
clearing routine handles both sides of the book by being given operator.ge or its opposite.
The simulation covers five years from a market open pinned at half past midnight today, and it
can either replay from a generated CSV or run in real time.
The HTTP layer under it is about thirty lines: a @route decorator that stamps a path pattern
onto a method, a do_GET that walks the handler class looking for a method whose pattern
matches the request path, and a BaseHTTPRequestHandler served on port 8080. One route,
/query, returns the current top of book for two stocks as JSON.
The three tasks
Task 1 — the price feed. The provided server3.py is a threaded HTTP market simulator
serving quotes on /query, generating a five-year walk with spreads between 2.0 and 6.0
and prices between 60 and 150. The work was on the client side: getDataPoint derives a
mid price from top_bid and top_ask, getRatio divides one stock's price by the other,
and the poll loop runs 500 requests against the server. Unit tests in client_test.py
cover the normal case and the inverted case where the bid comes in above the ask.
That second test is the point of the task. The naive implementation of a price is the ask, or the last trade, and both break when the book crosses. Defining price as the midpoint means the inverted case produces a sane number rather than an exception or a nonsense one, and the two tests assert exactly that by computing the expected midpoint independently of the function under test.
Task 2 — the viewer. A React/TypeScript app wrapping a FINOS perspective-viewer. The
table schema is declared up front (stock, top_ask_price, top_bid_price, timestamp),
the viewer is configured as a y_line chart pivoted by stock on columns and timestamp on
rows, with per-field aggregates. App.tsx was changed from a single fetch to a 100 ms
setInterval that streams new data into state continuously and clears itself after 1000
ticks.
The duplicate-row problem in that task is subtler than it looks. Perspective's table is
append-only from componentDidUpdate, so if the component holds a growing array and pushes
all of it on every tick, each row is inserted again on every render. The fix is upstream in
the state update, replacing the array rather than concatenating, and the aggregate settings on
the viewer then collapse repeated timestamps rather than drawing them twice.
Task 3 — the alert chart. The schema is rebuilt around what a trader actually watches:
price_abc, price_def, ratio, upper_bound, lower_bound and trigger_alert.
DataManipulator.generateRow computes both mid prices, divides them, fixes the bounds at
plus and minus 5%, and emits trigger_alert only when the ratio breaks out of that band,
leaving it undefined otherwise so the alert line stays blank until something is worth
looking at. The viewer plots ratio, both bounds and the alert on one axis.
The row also has to choose one timestamp from two quotes, and it takes the later of the pair, so a stale quote on one leg does not drag the series backwards. The stock pivot from task 2 is commented out rather than deleted, because the chart is now one derived series rather than two instruments.
Honest notes
Two rough edges are still in the checked-in code. The columns attribute in task 3 is a
string with an unbalanced quote around trigger_alert, which Perspective parses more
forgivingly than it should. And DataStreamer opens its XMLHttpRequest with the async flag
set to false, so every one of those 100 ms polls blocks the main thread while it waits; that
comes with the scaffold and I did not change it, but on a real feed it is the first thing that
would have to go.
This is coursework, and the write-up should not pretend otherwise. The engineering content is four small functions and about a dozen configuration lines against a codebase somebody else designed. What it is genuinely good for is the shape of the problem: bid-ask midpoints, a ratio between two instruments, a fixed band around it, and an alert that fires only on breakout.
Project Details
Virtual experience — four functions against a supplied codebase
2024
Coursework · Data