By Quant Blueprint · 7 min read
Python Quant Interview Questions: 5 Worked Data Tasks
Practice five original Python quant interview tasks with working solutions: weighted prices, point-in-time joins, rolling features, streams and array shapes.
A useful Python quant interview practice session ends with code you can run, a small example you can check by hand and an explanation of what could go wrong. Knowing the name of a pandas operation helps; choosing the correct rows and timestamps matters more.
These five original exercises focus on research data and implementation. They are Quant Blueprint practice questions, not questions attributed to an employer. Two Sigma's research interview guide names data analysis, coding and statistics as discussion areas. The tasks below bring those skills together without assuming every firm uses the same assessment.
Use Python 3. For the pandas and NumPy tasks, install those packages in your own practice environment. Try writing a complete solution before opening the answer. Explain your input contract aloud: missing values, ordering and grouping are part of the problem.
1. Calculate a weighted price without changing the denominator
You receive (symbol, price, quantity) rows. Return the quantity-weighted price for each symbol. Prices must be positive finite numbers; quantities must be finite and nonnegative. A zero-quantity row contributes nothing. If a symbol has no positive quantity, return None for it. Reject invalid rows rather than quietly excluding them.
For A, the rows are (100, 2) and (104, 1). For B, there is one row (50, 0). What should the function return?
Show answer and explanation
A's weighted price is (100 × 2 + 104 × 1) / 3 = 304/3, approximately 101.3333. B returns None. The ordinary mean of A's prices, 102, answers a different question.
from math import isfinite
def weighted_prices(rows):
totals = {}
for symbol, price, quantity in rows:
if not isfinite(price) or price <= 0:
raise ValueError("price must be positive and finite")
if not isfinite(quantity) or quantity < 0:
raise ValueError("quantity must be nonnegative and finite")
value, volume = totals.get(symbol, (0.0, 0.0))
totals[symbol] = (value + price * quantity,
volume + quantity)
return {symbol: value / volume if volume else None
for symbol, (value, volume) in totals.items()}
result = weighted_prices([
("A", 100, 2), ("A", 104, 1), ("B", 50, 0)
])
assert result == {"A": 304 / 3, "B": None}
assert weighted_prices([]) == {}
The loop takes linear time in rows and stores one pair per symbol. This reference assumes numerical input types and values small enough that intermediate products and sums remain finite. A monetary accounting system would also need an explicit precision and rounding policy.
Change the requirement: missing prices are now permitted. Would you reject the row, exclude both its price contribution and quantity, or return a missing result for the group? State the policy first. Dropping a missing price from the numerator while retaining its quantity in the denominator creates a different, usually unintended statistic.
2. Join each trade to a quote available at that time
Quotes have a symbol, quote time and price. Trades have a symbol and trade time. Match the most recent quote for the same symbol at or before the trade, provided it is no more than two seconds old.
For A, quotes arrive at seconds 1 and 4, priced 100 and 102. Trades at seconds 0, 3 and 5 should receive missing, 100 and 102. A B trade must never receive an A quote. For this exercise, timestamps represent availability, a same-time quote is usable, and each symbol has at most one quote at a given time.
Show answer and explanation
pandas merge_asof supports backward matching, group keys and an age tolerance. Both inputs must be sorted on the time merge key. Sorting by symbol first can leave time out of order globally.
import pandas as pd
quotes = pd.DataFrame({
"symbol": ["A", "A", "B"],
"quote_time": [1, 4, 2],
"price": [100, 102, 200],
})
trades = pd.DataFrame({
"symbol": ["A", "A", "A", "B"],
"trade_time": [0, 3, 5, 3],
})
joined = pd.merge_asof(
trades.sort_values("trade_time"),
quotes.sort_values("quote_time"),
left_on="trade_time", right_on="quote_time",
by="symbol", direction="backward", tolerance=2,
)
assert pd.isna(joined.loc[0, "price"])
# Inspect by symbol/time; merge output order follows sorted trades.
a = joined[joined.symbol == "A"].sort_values("trade_time")
assert a.price.iloc[1:].tolist() == [100, 102]
b = joined[joined.symbol == "B"]
assert b.price.tolist() == [200]
valid = joined.dropna(subset=["quote_time"])
assert (valid.quote_time <= valid.trade_time).all()
assert ((valid.trade_time - valid.quote_time) <= 2).all()
Retaining the quote timestamp makes the information boundary auditable. Test a trade before all quotes, one with an overly old quote, a different symbol and an exact timestamp match. If the original trade order matters, add a row identifier before sorting and restore that order after the join.
Change the requirement: the trade occurs before a quote with the same timestamp becomes observable. Set allow_exact_matches=False. If several quotes share one timestamp, introduce a reliable sequence or a declared aggregation rule; the timestamp alone may not determine what was available.
3. Build a trailing feature without including the current observation
Prices are [100, 102, 101, 105]. Before seeing each current price, compute the mean of the previous two observed prices. Require two observations, so the expected feature is [missing, missing, 101, 101.5].
Show answer and explanation
import pandas as pd
prices = pd.Series([100, 102, 101, 105])
feature = prices.shift(1).rolling(2, min_periods=2).mean()
assert feature.iloc[:2].isna().all()
assert feature.iloc[2:].tolist() == [101.0, 101.5]
The shift expresses the information boundary. A plain rolling mean includes the current row. Whether that is leakage depends on when the prediction is made; here the contract explicitly excludes that observation.
With multiple symbols, sort each symbol's observations chronologically and shift within the group. A global shift can import the last price of one symbol into the next symbol's feature. A simple grouped implementation is:
def add_feature(frame):
ordered = frame.sort_values(["symbol", "time"]).copy()
ordered["feature"] = ordered.groupby("symbol")["price"].transform(
lambda s: s.shift(1).rolling(2, min_periods=2).mean()
)
return ordered
Change the requirement: use the previous two seconds rather than two observations. Observation counts and elapsed time differ when events are irregular. Decide which endpoints are included, how missing intervals behave and what minimum history is necessary.
If the coding works but defining these boundaries feels difficult, book a free quant consultation to discuss your background, target roles and preparation priorities. For the statistical interpretation behind a pipeline, use the worked research statistics cases.
4. Maintain the mean of a streaming window
Values arrive one at a time. Return the mean of the last k values after each arrival, using the available history until the window fills. Require a positive integer k and finite numerical values.
Show answer and explanation
from collections import deque
from math import isfinite
def rolling_means(values, k):
if type(k) is not int or k <= 0:
raise ValueError("k must be a positive integer")
window = deque()
total = 0.0
for value in values:
if not isfinite(value):
raise ValueError("values must be finite")
if len(window) == k:
total -= window.popleft()
window.append(value)
total += value
yield total / len(window)
assert list(rolling_means([1, 3, 8, 4], 2)) == [1, 2, 5.5, 6]
assert list(rolling_means([], 2)) == []
assert list(rolling_means([7, 9], 1)) == [7, 9]
Each update performs constant queue work and uses at most k stored values. This reference assumes intermediate sums remain finite; finite inputs can still overflow a floating-point running total. Compare the output against a simple implementation that slices and recomputes each window. Floating-point addition and subtraction accumulate rounding error; discuss periodic recomputation or a more careful numerical method if accuracy requirements demand it.
Change the requirement: ignore missing observations. Does the window mean the last k arrivals or last k valid values? Those produce different answers. Also note that a generator validates lazily when iteration starts; merely calling it does not run its body.
5. Explain the shape before multiplying arrays
You have three observations of two assets. Returns have shape (3, 2) and fixed weights have shape (2,). Calculate one portfolio return per observation.
Show answer and explanation
import numpy as np
returns = np.array([[0.01, 0.02], [-0.01, 0.03], [0, -0.02]])
weights = np.array([0.25, 0.75])
portfolio = (returns * weights).sum(axis=1)
np.testing.assert_allclose(portfolio, [0.0175, 0.02, -0.015])
assert portfolio.shape == (3,)
The weights broadcast across observations, then summing across columns produces three results. With weights shaped (2, 1), the multiplication fails: the leading dimensions 3 and 2 are incompatible. NumPy's broadcasting documentation explains the right-to-left dimension comparison and warns that some broadcasted calculations create inefficient intermediate arrays.
Change the requirement: one asset return is missing. Returning missing, treating it as zero and reallocating its weight are different portfolio assumptions. Make the choice explicit and test a row with every asset missing.
Review the explanation as well as the answer
For each task, save the smallest input that exposed a mistake. Repeat with changed timestamps, grouping or missing-value rules. That is more informative than recognizing the same solution again.
Use the developer interview preparation guide for implementation and systems practice. For a longer research exercise, try the original take-home practice brief. If you need help deciding which skills fit your intended role, discuss your preparation in a free consultation.
Practice the Reasoning Behind the Answer
Bring your target roles and interview timeline to a free quant consult. Discuss your preparation priorities and how Quant Blueprint’s lessons, practice and mock interviews can help.
Book a Free Quant ConsultYour goals · your preparation · your next steps
Not ready yet? Grab the free prep pack.
Get our quant interview prep resources delivered to your inbox.