cswork / libraryOpen practice workspace

Interview guides · AllianceBernstein

AllianceBernstein Data Scientist Interview Questions & Guide 2026

Modeling drills for a AllianceBernstein data scientist interview: baselines, regression interpretation, class imbalance, model defense.

The 60-second brief

A Data Scientist at AllianceBernstein plays a pivotal role at the intersection of advanced technology, quantitative research, and global asset management. Operating in a highly sophisticated financial environment, you will build data-driven solutions that directly influence investment strategies, risk management, and client advisory services. The team leverages massive datasets to uncover hidden market trends, optimize portfolios, and automate complex decision-making processes, making this position highly impactful and strategically vital to the firm's success.

In modelling rounds the live question is usually why this model class for this problem, and how you would know six weeks after launch that it is still working. Deriving gradients by hand is rarely what is being probed.

Focus areas

  • Size positions by risk contribution, not conviction
  • Model impact and borrow before claiming capacity
  • Measure shortfall against arrival, not VWAP

Overview

Unlike traditional technology firms where data science operates in a silo, AllianceBernstein integrates its Data Scientists closely with portfolio managers, research analysts, and quantitative teams. You will work on diverse and challenging problem spaces, such as processing unstructured alternative data, refining predictive signals, and developing robust machine learning models to forecast asset performance. This close collaboration ensures that your quantitative models translate directly into real-world investment decisions and tangible business value.

For those who thrive on intellectual curiosity and complex problem-solving, this role offers an exceptional platform. You will have access to premier financial data infrastructure and the creative freedom to explore novel modeling techniques. Success in this role requires not only technical excellence in statistics and programming but also a keen interest in financial markets and the ability to communicate complex quantitative insights to non-technical stakeholders.

The AllianceBernstein interview process

AllianceBernstein candidates report 5 rounds · ≈ 4-6 weeks. The stages below are what candidates describe, not a published process.

Initial Phone Screen

Data Scientist covers at least four different jobs: experimentation, product analytics, causal work on observational data, and applied modelling that ships into a system. A screening call is the cheapest place to find out which of them is being hired for, and doing that diagnosis openly reads as senior rather than fussy. Ask what the last few pieces of work on the team actually were, and roughly how a week splits between querying, modelling and stakeholder time. Then say which parts of that you have done and which you have not. Claiming the whole range is the fastest way to be caught one round later.

What to demonstrate

  • Whether you can distinguish the flavours of the role and locate your own experience inside one of them honestly
  • Whether you name what you have not done instead of stretching to cover every line of the posting
  • Whether your hard constraints (notice period, location, work authorisation, level) surface now rather than at offer stage

How to prepare

  • Map the last two years of your time into rough percentages across query writing, experiment design, modelling and stakeholder work, so a question about scope has a real answer
  • Mark every responsibility in the posting as done, adjacent or new, and prepare one sentence for each adjacent item naming the closest thing you have actually built
  • Decide which logistics are non-negotiable before the call so you can state them in one sentence rather than negotiating live

Technical Check

This round decides whether someone can hand you a schema and a question and trust the number that comes back. Correctness under a clock is the bar, not clever syntax. The habit that separates strong from weak answers is checking the grain: after every join, know how many rows you expect and whether the count moved. Most wrong answers in this format are not wrong logic, they are a fan-out from a key that turned out not to be unique, or a filter applied before an aggregate when it belonged after. Say what you expect before you run it.

What to demonstrate

  • Whether your row counts survive each join, and whether you notice on your own when they do not
  • Deliberate handling of rows that fail to match, including whether the question needs an inner join or a left join with the non-matches kept and counted
  • Whether NULLs are treated on purpose, given that a NULL compares equal to nothing and that COUNT of a column skips it
  • Reaching a defensible answer inside the window instead of a refined one after it

How to prepare

  • Take a two-table schema, write a join that fans out on purpose, then fix it by collapsing the many-side to one row per key before joining. Repeat until the fix is reflex rather than recall.
  • Write a funnel as one query and print the distinct user count at each stage, then confirm each stage is a subset of the one above it rather than assuming it
  • Do a few timed runs in a plain text box with no autocomplete and no formatter, since assessment editors often have neither

Comprehensive Interview Loop

A loop is not scored one interview at a time. The people you meet compare notes afterwards, usually in a meeting you are not in, and the outcome turns on what each of them can say about you when asked. That rewards something other than survival: every room needs one specific thing worth repeating, and none of them can contradict another. The common way to lose is to tell the same project four times with different numbers in it, or to be uniformly fine in a way that leaves nobody with anything to argue for.

What to demonstrate

  • Whether your account of a project survives being told twice, with the same scale, the same metric definition and the same numbers each time
  • Whether each interviewer leaves with one concrete claim they could make on your behalf later, rather than an absence of complaints
  • Whether a question you already answered in an earlier room gets the same answer at the same depth, without visible impatience

How to prepare

  • Write a one-page fact sheet for your two or three main projects that fixes the numbers you will quote: rows of data, the metric as a single sentence, the effect you measured and how long the work took. Say them aloud from the sheet until they come out identical every time
  • For each kind of room you expect, decide the one sentence you want that interviewer repeating in a debrief, then check during the mock that you said it outright instead of implying it
  • Rehearse answering the same project question twice in one sitting, the second time as though you had not just answered it, because the thing that needs fixing is the flatness that creeps into a repeated story

Technical Evaluations

A handful of shapes account for most of what gets asked in this format: a ranking or deduplication inside groups, a running or rolling total, a period-over-period comparison, and a cohort tracked forward over time. Recognising the shape quickly is most of the speed here; deriving it from scratch while a clock runs is where the time goes. Know that a window function keeps every row while a GROUP BY collapses them, and know which one the question needs. If the exercise is in Python instead of SQL, the same shapes arrive as groupby with transform, shift and merge, and the same grain mistakes are available.

What to demonstrate

  • Whether you reach the right construct without a detour, such as ROW_NUMBER over a partition to deduplicate instead of a self-join against a MAX subquery
  • Whether you know what your window frame actually is, since adding ORDER BY inside OVER changes the default frame and silently changes a running total
  • Whether the thing runs. A near-miss that throws an error scores below a plainer query that returns the right rows.

How to prepare

  • Write each of the four shapes once from memory against a small schema and keep the working version somewhere you will reread it: dedupe with ROW_NUMBER, a running total, a month-over-month change with LAG, and a retention table
  • Compute one running total twice on data with tied timestamps, once on the default frame and once with ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW, and look at where the two disagree
  • If Python is on the table, rebuild the dedupe and the running total with groupby and cumsum, then assert the two implementations return identical rows

Behavioral Interviews

This round decides whether you owned a decision or watched one happen nearby. Interviewers for data roles listen for the point where the analysis stopped being a report and started changing what someone did, so build each story around that hinge: what was going to happen by default, what you found, and what happened instead. The most common weakness is a story that ends at delivery. If you can name the decision your work changed and the number that moved because of it, most follow-ups become easy.

What to demonstrate

  • Whether the decision was yours to influence, or whether you are narrating a team outcome in the first person
  • The counterfactual: what would have been done without your analysis, and why that default was worse
  • How far your involvement ran past the handoff, and whether you checked that the change did what you predicted

How to prepare

  • Pick three projects and write one sentence for each naming the decision-maker, the choice in front of them, and what they chose after seeing your work. If you cannot name a person and a choice, the story is not ready for this round.
  • Reconstruct the baseline for your strongest project from the original query or dashboard rather than memory, so the before-number survives a follow-up asking where it came from.
  • Prepare an honest version of a project where your recommendation was overruled, including what you did with the analysis afterwards.

Where candidates lose points

Reporting the best backtest out of many trials as if it were a single pre-registered test.

The maximum of N noisy Sharpe estimates grows roughly like the standard error times sqrt(2 ln N) even when every underlying strategy has zero edge, so with a few hundred variants an in-sample Sharpe near 1 is the expected result of pure noise. Worse, the search is rarely counted honestly: parameter sweeps, universe changes, date-range choices and feature variants all count as trials. Quote the number of configurations tried, deflate the Sharpe for it, and keep a genuinely untouched holdout period. Note also that the asymptotic standard error of a Sharpe estimate is approximately sqrt((1 + SR^2/2)/T) for i.i.d. normal returns, which for three years of daily data is roughly 0.33, so two strategies differing by 0.3 in Sharpe are not distinguishable.

Judging execution quality against interval VWAP and treating a favourable number as proof of good trading.

Interval VWAP is a benchmark the trader partly determines: trading in line with volume tracks VWAP almost by construction, and stretching an order over a longer interval makes the benchmark easier while exposing the position to price drift that the benchmark never charges. Arrival price is the benchmark aligned with the decision, because it charges both the spread and the drift between decision and completion, including the unfilled remainder. Reporting both, and reporting the opportunity cost of unfilled quantity, is what separates a real TCA from a flattering one.

Answering a product-sense question with a list of features

Answer with a decision and the measurement that would settle it: the hypothesis, the primary metric, the guardrails, and the result that would make you not ship. A feature brainstorm cannot be wrong, which is exactly why it earns no points.

Sizing estimates built on unnamed, unrevisable assumptions

Write each assumption as a named number you can change, then show the arithmetic so the interviewer can challenge one input instead of the whole answer. Finish by saying which assumption the result is most sensitive to, which matters more than the point estimate.

Questions & practice

How do you calculate the conditional probability of an event, and can you explain Bayes' Theorem using a financial use case?

Medium · statistics and probability

Approach

  1. Say what the estimate is of, and over what population it generalises.
  2. Translate the result into the decision it informs, in one plain sentence.
  3. Write down the assumption the method needs before you use the method.

Follow-up

  • What sample size would you need to detect an effect half this size?
  • Which assumption here is most likely to be violated in practice?

Explain the concept of matrix diagonalization and how it is applied in principal component analysis (PCA).

Medium · statistics and probability

Approach

  1. Sanity-check the answer against a simple bound or a simulated case.
  2. Write down the assumption the method needs before you use the method.
  3. Quantify uncertainty explicitly rather than reporting a point estimate alone.

Follow-up

  • Which assumption here is most likely to be violated in practice?
  • How would you explain this result to someone who does not know statistics?

How would you design a hypothesis test to determine if a new trading signal significantly outperforms an existing benchmark?

Medium · statistics and probability

Approach

  1. Sanity-check the answer against a simple bound or a simulated case.
  2. Say what the estimate is of, and over what population it generalises.
  3. Write down the assumption the method needs before you use the method.

Follow-up

  • What sample size would you need to detect an effect half this size?
  • How would you explain this result to someone who does not know statistics?

Explain the Central Limit Theorem and its practical significance when analyzing volatile market data.

Medium · statistics and probability

Approach

  1. Translate the result into the decision it informs, in one plain sentence.
  2. Say what the estimate is of, and over what population it generalises.
  3. Quantify uncertainty explicitly rather than reporting a point estimate alone.

Follow-up

  • Which assumption here is most likely to be violated in practice?
  • How would you explain this result to someone who does not know statistics?

Explain the bias-variance tradeoff and how it guides your choice of model complexity.

Medium · machine learning and modelling

Approach

  1. Frame the prediction: the label, the moment of prediction, and the action it triggers.
  2. Pick an evaluation metric that matches the cost of each error type, not a default.
  3. Set a baseline first, so any model has something honest to beat.

Follow-up

  • Where could label leakage enter this setup?
  • How would you choose the decision threshold, and who owns that choice?

How do you address the issue of overfitting when training a machine learning model on historical financial time-series data?

Medium · machine learning and modelling

Approach

  1. Set a baseline first, so any model has something honest to beat.
  2. Say how the offline result would be validated online before it is trusted.
  3. Pick an evaluation metric that matches the cost of each error type, not a default.

Follow-up

  • What would you monitor after launch to know the model is still valid?
  • How would you choose the decision threshold, and who owns that choice?

Validate a point-in-time signal panel before it is traded

Easy · data quality · point-in-time · pandas

signal_score arrives as a DataFrame with signal_id, model_version, instrument_id, as_of_date, knowledge_ts (UTC), raw_value, zscore_xs, decile_rank, universe_id, coverage_flag, is_backfilled and computed_at: one signal, six years, about 2.4M rows. Write a checker returning a tidy frame of (check_name, as_of_date, n_violations, example_instrument_id) covering grain duplication, knowledge_ts ordering, the cross-sectional moments of zscore_xs, effective coverage, and day-over-day universe churn. Choose your own thresholds and state each one in the output.

Approach

  1. Start with the grain. The declared key is (signal_id, model_version, instrument_id, as_of_date). Count rows per key rather than calling duplicated(), which with keep='first' reports k-1 for k copies and tells you nothing about whether the copies disagree. Compare raw_value across the duplicates to separate a harmless double-load from two model outputs colliding.
  2. Check the two timestamp orderings, both of which are silent lookahead: knowledge_ts earlier than the close of as_of_date means the row claims to know a day's value before the day ended, and knowledge_ts later than computed_at is impossible, since inputs cannot become observable after the job that read them ran.
  3. Per as_of_date, take the moments of zscore_xs. It is winsorized at +/- 3 within universe_id, so the mean should sit near zero and the standard deviation a little under one; flag |mean| > 0.05 or std outside [0.85, 1.15]. Separately verify decile_rank is monotone in zscore_xs within the date and that each decile holds n/10 plus or minus one name.
  4. Measure effective coverage, not row coverage: put only coverage_flag = 'computed' in the numerator, and additionally flag instruments whose raw_value is unchanged for more than ten consecutive as_of_dates, which catches a feed that stopped updating without emitting a single null.
  5. Compute universe churn as the symmetric difference of the instrument sets on consecutive as_of_dates over the mean of the two sizes. Outside index reconstitution this should sit well under 1% a day, so flag above 5%. Report the is_backfilled share on the same frame: a spike is a rerun that rewrote history in place.

Worked solution (25 min)

  1. Sort by (instrument_id, as_of_date). Build grain counts with groupby(key).size() and keep keys above 1.
  2. Express the two timestamp checks as boolean columns, then aggregate per as_of_date with sum for the count and idxmax for the example instrument.
  3. Per-date moments via groupby('as_of_date')['zscore_xs'].agg(['mean','std','count']), plus a Spearman check of zscore_xs against decile_rank within each date.
  4. Staleness runs: group by instrument_id, compare raw_value to its shift(1), start a run id with a cumsum over the change flag, and take run lengths.
  5. Universe churn from consecutive-date sets built with groupby('as_of_date')['instrument_id'].agg(set) and differenced pairwise.

Expected result: A tidy frame where most dates carry zero violations, index reconstitution dates show a churn spike that you explain rather than flag as a defect, and any date spanning a model_version changeover shows both a grain-duplication burst and a moment shift. Those two appearing together identify a rerun that overlapped the live daily job.

Verify it yourself

  • Rows in grain violation plus clean rows must sum to the input row count.
  • The number of distinct as_of_date values in the report never exceeds the number in the input.
  • On a date you have inspected by hand and know to be clean, every check returns zero; a check that never returns zero on any date has a wrong threshold, not bad data.

Follow-up

  • A date shows 4% of rows with knowledge_ts before the as_of_date close. How do you decide between dropping those rows, shifting their knowledge_ts, and quarantining the whole date?
  • Which of these checks belongs in the daily job as a blocking gate and which as a report, and what does a false positive cost in each case?
  • How would the thresholds change for a signal whose eligible universe is 120 names rather than 1,500?

Reconstruct a point-in-time universe from effective-dated reference data

Easy · scd2 · point-in-time · temporal-join

instrument_master is effective-dated: one row per instrument_id per version, valid over [effective_from, effective_to), with effective_to NULL while the version is open. Other columns include ticker, asset_class, primary_exchange_mic, status, is_shortable, listing_date and delisting_date. Write one query returning every instrument that was a listed, shortable equity as of 2023-06-30 17:00 UTC: one row per instrument_id, carrying the ticker in force at that instant. The result must still contain names that have delisted since.

Approach

  1. Predicate on the validity window rather than on the latest row: effective_from <= :ts AND (effective_to > :ts OR effective_to IS NULL). The half-open form is what guarantees exactly one row per instrument_id when one version ends at the same instant the next begins.
  2. Read the attributes from that version, not from today's row: asset_class = 'equity', is_shortable, and status as recorded in the version. Today's status is the wrong fact for a past date.
  3. Bound the lifecycle separately: listing_date <= :ts::date AND (delisting_date IS NULL OR delisting_date > :ts::date), so pre-IPO shells and names already dead on that date drop out on that date's own terms.
  4. Probe uniqueness with GROUP BY instrument_id HAVING COUNT(*) > 1. A duplicate means the source has overlapping versions, which is a data-quality finding to escalate, not something to paper over with DISTINCT.
  5. Confirm the temporal behaviour by re-running with the timestamp moved past a known delisting and checking the name disappears then, and only then.

Worked solution (20 min)

  1. Write the range predicate as a CTE parameterised on the cutoff timestamp so the same text is reusable per date.
  2. Add the attribute filters inside the CTE, then the listing/delisting bounds in the outer select.
  3. Run the duplicate probe on instrument_id and confirm it returns no rows.
  4. Re-run with effective_to IS NULL substituted for the range predicate and diff the two result sets.
  5. Inspect the diff: every row present only in the correct version should have a non-null delisting_date or a later reference-data amendment.

Expected result: One row per instrument_id whose open version at 2023-06-30T17:00Z had asset_class = 'equity' and is_shortable = TRUE, including instruments whose current version now reads status = 'delisted'.

Verify it yourself

  • COUNT(*) equals COUNT(DISTINCT instrument_id).
  • No returned row has delisting_date <= DATE '2023-06-30'.
  • At least one returned row has a non-null delisting_date, proving the survivor-only failure did not occur.
  • The effective_to IS NULL variant returns a different set, and every row it drops is explainable by a delisting or a later amendment.

Follow-up

  • The vendor backdates a correction, so effective_from for a new version precedes an existing one and your query returns two rows for one instrument_id. How do you resolve it, and what does loaded_at let you express that effective_from cannot?
  • Someone proposes joining research panels on ticker because it is human-readable. Give a concrete failure mode with a date attached.
  • How would you turn this single-date query into one that produces the universe for every date in a range without a loop?

Order fill funnel by execution algorithm, in counts and notional

Easy · funnel · conditional-aggregation · cohorting

From parent_order (order_id, algo_name, order_qty, filled_qty, status, arrival_ts, arrival_mid_px), build a monthly funnel per algo_name over orders whose arrival_ts falls in the month: orders released, orders with any fill (filled_qty > 0), orders fully filled (status = 'filled'), and the two step conversion rates. Produce the same funnel in notional, using order_qty * arrival_mid_px. Orders with status = 'rejected' never reached the market — state how you treat them and implement that choice.

Approach

  1. Use conditional aggregation in a single pass — COUNT(*) FILTER (WHERE filled_qty > 0) and friends — rather than three subqueries joined together, so every numerator and denominator provably comes from the same row set.
  2. Make the denominator decision explicit: rejects are an order-management outcome, not an execution outcome, so exclude them from both numerator and denominator and report their count in a separate column. An exclusion you cannot see in the output is an exclusion nobody can audit.
  3. Cast before dividing. With integer counts, filled / released floors to 0 in Postgres; filled::numeric / NULLIF(released, 0) gives the rate and also handles an algo with zero orders in the month.
  4. Compute the notional funnel with the same FILTER clauses over SUM(order_qty * arrival_mid_px). Divergence between the count funnel and the notional funnel is the finding: 60 percent of orders completing but 20 percent of notional means the large orders are the ones stalling.
  5. Group by date_trunc('month', arrival_ts) and algo_name, and left-join a distinct algo list if months with zero activity must still appear as rows.

Follow-up

  • filled_qty sits on the parent but the fills live in execution_fill. How would you find parents whose filled_qty disagrees with the sum of their non-correction fills, and which side would you trust?
  • The fully-filled rate rises after participation_rate_target is lowered. Is that an improvement, and what second metric settles it?

Design a firm-level client-outcome metric that survives audit

Hard · survivorship · aum-weighting · guardrails · client-outcomes

The firm wants one number reported monthly to the board for whether clients are getting what they were sold. The proposal is: AUM held in accounts whose trailing 36-month net-of-fee return beats their own benchmark_id, divided by AUM in accounts with at least 36 months of history. Using account_mandate (benchmark_id, inception_date, funded_date, close_date, status, mgmt_fee_bps, perf_fee_rate, high_water_mark_base, effective_from, effective_to) and position_daily, critique it, name three ways it rises without any client being better off, and specify the guardrails and the exact denominator you would publish.

Approach

  1. Attack the denominator first, because that is where this metric is won or lost. Requiring 36 months of history excludes accounts that closed during the window, and accounts close disproportionately after bad performance. Fix it by including accounts whose close_date falls inside the window, measured to their close, and carrying them for 36 months afterwards, so the series cannot be improved by attrition.
  2. Name the three inflation paths concretely. First, survivorship through the close_date exclusion. Second, benchmark choice: benchmark_id is per account and amendable, and account_mandate is SCD2, so an amendment silently restates history unless the metric evaluates each date against the benchmark in force via effective_from and effective_to, with a count of amendments published beside the ratio. Third, AUM-weight concentration: one large mandate can carry the number, so publish the effective number of accounts as the inverse Herfindahl of AUM weights next to it.
  3. Set the guardrails from the spine: cross-sectional dispersion of trailing 12-month net return within each strategy composite, and trailing 12-month dollar redemption rate. Dispersion catches the case where the headline is met by a favoured subset, and the redemption rate catches the attrition path from the client's side of the relationship.
  4. Fix the numerator's arithmetic. Net of fee means management fee accrued plus performance fee crystallised, and under a high-water mark with a hurdle two accounts in one strategy legitimately pay different fees, so compute per account from its own terms rather than deducting a composite fee. Then decide the return convention deliberately: time-weighted answers whether the manager delivered, money-weighted answers whether the client made money, and publishing only one answers only one question.
  5. State the residual honestly. Even a clean version measures relative return, not whether the product matched the client's purpose. Pair it with a small set of hard flags from the same tables, such as breaches of max_tracking_error_bps or max_single_name_weight_pct and recon_status values other than 'matched', rather than pretending one ratio covers the question.

Worked solution (45 min)

  1. Build the account-month panel from account_mandate with SCD2 ranges resolved, tagging each account-month with the benchmark_id in force that month rather than the current one.
  2. Compute per-account trailing 36-month net-of-fee time-weighted return and the matching benchmark return over the same dates, including accounts closed inside the window up to their close_date.
  3. Compute the headline ratio under three denominators, surviving accounts only, surviving plus closed-to-date, and surviving plus closed with a 36-month tail, and tabulate the gaps.
  4. Compute the guardrails: within-composite standard deviation of trailing 12-month net return, trailing 12-month dollar redemption rate, and the effective number of accounts as one over the sum of squared AUM weights.
  5. Recompute the headline as of a month six months in the past and compare it to what was published then; any drift is a restatement and needs a named cause.

Expected result: A published specification plus evidence that the survivorship gap is material, with the surviving-only version typically reading several percentage points above the closed-inclusive version, and a board card carrying four figures on one line: the ratio, the effective number of accounts, within-composite dispersion and the trailing 12-month dollar redemption rate.

Verify it yourself

  • Surviving-only and closed-inclusive versions must differ. Identical values mean either no account closed in the window or close_date is not populated.
  • The effective number of accounts must be below the raw account count, and far below it when one mandate dominates the AUM.
  • Re-running a past month must reproduce the number published then. If it does not, something restated: a benchmark amendment, a late fee accrual, or a reconciliation break.
  • Each account's net return must reconcile to its gross return minus its own accrued fees; a composite-level fee deduction will not tie under a high-water mark.

Follow-up

  • An account funded 20 months ago has beaten its benchmark throughout and is excluded by the 36-month rule. Is that the right treatment, and what would including it cost you?
  • Two accounts in the same composite differ by 180 basis points over 12 months. List the legitimate causes before calling it an error.
  • How does the published ratio behave in a month when the firm funds one very large new mandate, and what should the board see alongside it?

Size an execution algorithm test on implementation shortfall

Easy · power · mde · execution

Per-order implementation shortfall against arrival price, computed from parent_order (order_qty, filled_qty, arrival_mid_px, avg_fill_px, adv_20d) and execution_fill (commission_amt, exchange_fee_amt, rebate_amt), has averaged 12.4 bps with a standard deviation of 35 bps over the trailing twelve months. The desk releases roughly 1,900 parent orders a month. An execution team proposes a new child-order scheduler and claims a 1.5 bps improvement. Size a two-arm, order-level randomised test at 80 percent power and a two-sided 5 percent level, and say how long it has to run.

Approach

  1. Pin the response variable to the exact metric definition before touching the arithmetic: shortfall in bps versus arrival_mid_px, with commission, exchange fee and rebate included and the unfilled remainder (order_qty minus filled_qty) charged at the terminal mid. The sigma you plug in has to be the standard deviation of that quantity, not of a convenient proxy.
  2. Estimate sigma from the trailing twelve months of the same order population rather than from a remembered number. Shortfall is heavy tailed, so also compute a winsorised sigma and quote the sample size both ways, because the difference tells you how much of your power calculation rests on a handful of orders.
  3. Apply the two-sample difference-in-means formula, n per arm = 2 * sigma^2 * (z_{0.975} + z_{0.80})^2 / delta^2, which under equal allocation reduces to 15.7 * sigma^2 / delta^2.
  4. Decide whether the shipping decision is made on the equal-weighted per-order mean or the notional-weighted mean, because they have different variances. For a weighted mean the effective sample size is (sum of weights)^2 / (sum of squared weights), and notional weights in a book with a long tail of large orders can cut that well below the row count.
  5. Convert to calendar time at realised flow and check the answer against the business horizon. If nine months is unacceptable, the legitimate levers are a larger delta, lower power accepted explicitly, or variance reduction; shortening the test while keeping the same stopping rule is not one of them.

Follow-up

  • What happens to the required sample size if the committee decides on the notional-weighted mean instead of the equal-weighted mean?
  • At 1,900 orders a month, what effect size can you detect in a single month, and would you run that test at all?
  • If the real goal is 'no worse than the incumbent', how do you set the non-inferiority margin and what changes in the test?

Information ratio rose while gross alpha stayed flat

Hard · autocorrelation · marking · risk measurement · annualization

The book's trailing-252-day net information ratio rose from 0.90 to 1.25 over a quarter. Mean daily active return is unchanged within its standard error, and realized annualized tracking error fell from 430 bps to 310 bps. Nothing in portfolio construction or the risk limits changed. Tables: position_daily (business_date, account_id, instrument_id, quantity, mark_px, mark_source, market_value_base, unrealized_pnl_base_day, realized_pnl_base_day, recon_status) and a daily benchmark return series. Decide whether risk genuinely fell, and state what you would tell the risk committee before anyone re-levers the book.

Approach

  1. Split the ratio and confirm the entire move sits in the denominator. An information ratio that improves with a static numerator is a statement about measured volatility, and measured volatility is a property of how the return series was constructed as much as of the positions held.
  2. Test the series before testing the positions. Compute the autocorrelation of daily active return in each window. Smoothed or carried-forward marks induce positive autocorrelation, and the sqrt(252) annualization assumes it is zero, so a positive rho makes annualized volatility too small by construction rather than by chance.
  3. Compare volatility across sampling frequencies. Aggregate the same daily active returns into non-overlapping weekly and monthly returns and annualize each. Under independence the three estimates agree. Under smoothing the daily estimate is the smallest and the gap widens with horizon. This is the decisive test because it needs no view on which positions are mismarked.
  4. Only then find the mechanism: tabulate the share of gross market value by mark_source per business_date, and the share of rows whose recon_status is not 'matched'. A rising share of vendor_eval, model or stale_prior_day marks is the direct cause, and the affected names are usually the least liquid in the book.
  5. Give the committee the unsmoothed volatility and the corrected information ratio next to the reported ones, and say plainly that sizing to a volatility target computed on smoothed marks applies real leverage to understated risk.

Follow-up

  • With a first-order autocorrelation of 0.25 and no higher-order terms, by how much is long-horizon volatility understated by the daily estimate? Show the arithmetic.
  • Which instruments in this book would you expect to carry a vendor_eval mark, and what does that imply about the liquidity of the risk you just re-measured?
  • The same smoothing inflates the Sharpe ratio. What does it do to reported maximum drawdown, and in which direction?

Suggested practice checklist

For someone who has spent the last year in notebooks, dashboards or modelling work and has not written raw SQL under time pressure. The first four days rebuild query fluency against a fixture you control and can verify by hand; the last three attach that fluency to the rest of the loop.

Day 1 — Build a fixture you can check answers against

  • Create a local Postgres or SQLite database with four tables (users, sessions, events, orders) holding roughly 200 rows you generated yourself, so you know the contents well enough to predict every result.
  • Deliberately seed the cases that break queries: a user with no sessions, a session with no events, two orders sharing a timestamp, a NULL in one join key, and one duplicated user row.
  • Before writing any SQL, hand-compute five answers on paper (how many users placed at least one order, median orders per ordering user, and three others) and save them as the ground truth for the week.

Deliverable: A one-command seed script plus a text file of five hand-computed answers to grade every later query against.

Day 2 — Joins, filters and NULL semantics

  • Answer "which users have no orders" three ways (LEFT JOIN with IS NULL, NOT EXISTS, NOT IN) and confirm that the NOT IN version returns zero rows once the subquery contains a NULL, because the comparison is never TRUE.
  • Reproduce the LEFT JOIN that silently collapses to an inner join by putting a right-table predicate in WHERE, then fix it by moving the predicate into the ON clause, and record both row counts.
  • Create a fan-out bug on purpose by joining orders to order_items and summing the order total, then correct it with a pre-aggregated subquery and explain in one line which table changed the grain.

Deliverable: One annotated .sql file holding the three join traps, each with the wrong result and the corrected result side by side.

Day 3 — Window functions and frames

  • Write three window queries against the fixture: a running order total per user, the rank of each order within its user by value, and the day gap to that user's previous order, then check each against the day-one ground truth.
  • Run ROW_NUMBER, RANK and DENSE_RANK over a column containing ties, print all three side by side, and write one sentence on when each is the correct choice.
  • Switch one query from the default frame (RANGE BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW, which is what you get when ORDER BY is present and no frame is written) to ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW, and explain why the output differs only when the ORDER BY column has duplicates.

Deliverable: Three verified window queries plus a short note explaining the RANGE versus ROWS difference in your own words.

Day 4 — The four analytical query patterns

  • Write a monthly retention grid: first order month per user, then months-since-first as the column, and verify that month zero equals the cohort size exactly.
  • Sessionize the events table under a 30-minute inactivity rule using LAG plus a cumulative sum over a new-session flag.
  • Build a four-step funnel that counts distinct users rather than events at each step, and state the rule you applied to a user who reaches step three without ever logging step two.

Deliverable: One file with the retention, sessionization and funnel patterns, each carrying a one-line note on the assumption it bakes in.

Day 5 — Write SQL the way you will have to write it live

  • Set a 12-minute timer and solve three medium prompts in a plain editor with no execution and no autocomplete, then run them and tally syntax errors separately from logic errors.
  • Narrate one solution aloud while writing it, stating the grain of each intermediate result (one row per user, one row per user-day) before you type its body.
  • Rewrite your slowest solution as a CTE chain where every CTE name states its grain, and time yourself re-solving it from blank.

Deliverable: A recording of one narrated solution plus an error tally that separates syntax from logic.

Day 6 — One day for everything that is not SQL

  • Write the preconditions of the two-sample t-test from memory, then check them: independent observations, and a difference in means whose sampling distribution is approximately normal, which at large sample sizes follows from the central limit theorem rather than from normality of the raw values.
  • Write the difference between an odds ratio from logistic regression and a relative risk, and state the condition under which the two are close (low outcome prevalence).
  • Prepare a 90-second answer to "how would you know this model is any good" that names the metric, the baseline you would beat, and the cost of the errors you care about.

Deliverable: One page of notes covering test preconditions, the odds-ratio caveat and the model-quality answer.

Day 7 — Full loop rehearsal

  • Run a 45-minute mock with someone willing to interrupt: 20 minutes of SQL, 15 minutes defining a metric, 10 minutes on a past project.
  • Re-solve from blank the two queries you were slowest on this week and compare the times against day five.
  • Write a five-line answer to "walk me through a project" that puts a number in the first sentence and names the decision the work changed.

Deliverable: Mock feedback notes plus a timed project narrative you can deliver without reading it.

Stories where someone disagreed with you

Have two ready. In one, the data was on your side and you had to move someone who outranked you. In the other, the pushback was correct and you changed position. The second is the harder story and it lands better, because it shows you separate being right from being attached to an answer. Name the person's actual objection.

Why are you interested in Quantitative Research (QR) and asset management at AllianceBernstein?

Medium · behavioural and stakeholder questions

Approach

  1. Close with what you would do differently, concretely.
  2. Name the disagreement or constraint, and how you resolved it with evidence.
  3. Quantify the outcome, including what you would not claim credit for.

Follow-up

  • What did you decide not to do, and why?
  • How did you know the outcome was caused by your change?

Tell me about a time you had a professional conflict with a team member or stakeholder. How did you resolve it, and what did you learn?

Medium · behavioural and stakeholder questions

Approach

  1. Name the disagreement or constraint, and how you resolved it with evidence.
  2. Quantify the outcome, including what you would not claim credit for.
  3. Close with what you would do differently, concretely.

Follow-up

  • What would you do differently if you ran that project again?
  • How did you know the outcome was caused by your change?

Explaining why two accounts in one strategy diverged 210 bps

Easy · client communication · attribution · reconciliation

Two separately managed accounts run the identical strategy. Over the trailing twelve months one returned 8.4 percent net and the other 6.3 percent, a gap of 210 bps. The client who owns the lower one has asked in writing why. You have position_daily, account_mandate (SCD2) and the fill history for both accounts. Produce two things: a reconciliation that accounts for the gap down to a residual you state, and a reply of at most 200 words that a non-specialist can act on, without jargon and without blaming the client.

Approach

  1. What is probed: whether you reconcile to the total before you explain anything. An explanation that does not sum to the observed gap is a guess with numbers attached, and the client's next analyst will find the difference.
  2. Reconcile mechanically and in this order, because each step has a clean source: fee difference from mgmt_fee_bps plus any performance fee crystallised above the high-water mark; average cash weight, as one minus the summed weight_pct_nav; restricted names, as is_restricted days multiplied by what those names returned; single-name cap differences across account_mandate SCD2 versions; and the ramp between funded_date and the date gross exposure first reached 90 percent of target.
  3. Carry a residual and state it plainly. If five components explain 170 of 210 bps, the reply says 40 bps unexplained rather than largely explained by. The residual is usually trade timing across the two accounts, and naming it is cheaper than having it discovered.
  4. Order the reply by what the client can do rather than by size of component: what is structural and will persist, such as the fee schedule and their own restricted list; what was one-off and will not repeat, such as the ramp; and what they can change if they choose to.
  5. Remove every term that would need looking up. Your restricted list kept the account out of three names that contributed 62 bps is actionable. Negative selection effect from compliance constraints is not, and the decision to relax a restriction belongs to the client rather than in a recommendation you push.

Follow-up

  • The residual is 120 bps rather than 40. What goes in the letter, and what do you do before sending it?
  • The client asks whether their account was deliberately disadvantaged. How do you answer that specific question?
  • Does the other client need to be told anything, and who decides?