Tasks/Finance, Law & Business

Equity DCF Value-Driver Forecasting

Forecast growth, margins, reinvestment, WACC, and terminal growth for unseen companies from messy fundamentals

Finance, Law & Businessequity valuationforecasting
Background

Quantitative equity research runs on discounted-cash-flow models whose mechanics are settled; the open problem is forecasting each company's forward value drivers. The starting point is a coverage desk's first-pass forecaster, which reverts each company's latest growth, margin, and reinvestment toward its sector average and sets a flat discount rate. The work is to replace it with an estimator fitted across companies whose realized drivers are known. Histories are short, patchy, and distorted by one-off accounting years, and any gain must hold on sealed companies.

instruction.mdthis is what the agent is given

You have inherited a buy-side equity-research desk's intrinsic-value model. The desk values companies with a fixed, trusted discounted-cash-flow (DCF) engine that turns a set of forward value-drivers into a per-share value; the open problem is forecasting those drivers for every name in the coverage universe. You inherit a deliberately weak first-pass model (a sector-reversion blend) plus a second weak stab and a few notes — improve one or write a better method. Your forecaster is re-run on a sealed held-out universe whose true drivers you never see, and scored by how close the DCF values it implies land to the truth: lower error is better.

Hard Constraints

  • Submit an algorithm (solve(problem)), not precomputed drivers — the grader re-runs your code on hidden companies.
  • Keep the exact signature solve(problem) -> {company_id: drivers}, returning a record for every id in problem.holdout_ids and no others.
  • Each drivers record must be well-formed and in range: rev_growth, ebit_margin, reinvest are 5-element lists; wacc and terminal_growth are scalars; and terminal_growth < wacc (the Gordon terminal requires it). A malformed / out-of-range / wrong-id-set submission scores 0.
  • solve is handed a problem object; it must not read data files or look up answers — the held-out truth is sealed in the grader and there is no network at grade time.
  • Grading budget (stated so you can size your method). Your solve is run once, in a fresh subprocess capped at a 1200 s wall-clock budget, inside a container declared at 2 CPUs / 512 MiB; the whole verifier stage is capped at 3600 s. Your own agent container is the same size (2 CPUs / 512 MiB). Relative to your own self-check the grading side is about 0.1x the wall clock (measured end-to-end at 2 CPUs / 512 MiB: 4.85 s of fit_calibration.py versus ~0.5 s for the whole grading stage) — grading is cheaper because the self-check refits your method 60 times (leave-one-out over the 60 calibration names) while grading calls solve exactly once. Do not read that as slack in the other direction: that single call must produce drivers for 4x as many companies (240 held-out versus 60 calibration names), so a method whose cost grows with the universe size sees 4x the work in one shot. A run killed by the time or memory cap produces no drivers and scores 0; the grader records the timeout and the subprocess's exit status separately from "produced no output", so both show up in the run log.

What You Have

  • The workspace /app/ as your predecessor left it (/app/README.md orients you):
  • Data (/app/data/): coverage.csv (per-company as-of facts + metadata), fundamentals.csv (the messy observable per-year history), calibration_truth.csv (realized drivers + true value for the calibration names only), and data_dictionary.md. Read the dictionary — histories vary in length, cells are missing (MNAR), some fiscal years carry one-off special_items, and sector / beta_proxy are sometimes blank; how you treat the mess is a large part of the task.
  • The public DCF bridge (/app/lib/): valuation.py / fin_compute_engine.py value a driver record with the same mechanics the grader uses (you may import them for your own checks); paneldata.py builds the problem object.
  • The editable baselines (/app/methods/): main/forecaster.pythis directory is what gets graded — is a weak sector-reversion blend; own_history_trend/ is a second, different weak stab. Improve one in place, or rewrite the method entirely.
  • Your self-check surface (free, unlimited): python /app/fit_calibration.py /app/methods/main refits your method leave-one-out over the visible calibration names — fit on the other 59, forecast the one held back, rotated over all 60 — and reports the median absolute out-of-sample valuation error. Pass --kfold K to spend K fits instead of 60 if your method is slow. This matches the grading regime, where your method fits on all visible calibration truth before forecasting the sealed names. This is your only feedback loop, and it is a proxy: the sealed grade is on other companies, so a low calibration error is necessary but not sufficient.

What You Submit

Edit /app/methods/main/forecaster.py to expose this exact signature:

def solve(problem) -> dict:   # {company_id: drivers} for every id in problem.holdout_ids

where each drivers record is:

{"rev_growth": [g1, g2, g3, g4, g5],   # revenue growth per year (0.12 = +12%)
 "ebit_margin": [m1, m2, m3, m4, m5],  # EBIT margin per year    (0.25 = 25%)
 "reinvest":    [r1, r2, r3, r4, r5],  # reinvestment as a fraction of NOPAT, in [0,1)
 "wacc": 0.09,                         # discount rate, in (0,1)
 "terminal_growth": 0.025}             # long-run growth; MUST be < wacc

problem exposes holdout_ids, load_companies() (each a dict with id / set / sector / beta_proxy / facts / history / ipo_year / …), companies_by_id(), and load_calibration_truth() (realized drivers + true value for the calibration names — your fitting data). You may add helper modules next to forecaster.py or restructure the method entirely; the whole methods/main/ directory runs. The runtime provides numpy. There is no submit step and no per-attempt feedback — self-check for as long as your run window allows, then leave your best forecaster.py in place; it is graded once at the end on the hidden universe.

How It Is Judged

After your run, the grader runs your solve on the sealed held-out companies' observable panel (no truth), then recomputes — with its own sealed engine — each company's intrinsic value V_hat from your drivers and the perfect-foresight value V* from its hidden realized drivers, and scores the panel median absolute valuation error:

MAVE = median over a sealed TEST subset of the holdout companies of
           |V_hat - V*| / V*                                        (per-company error capped at 2.0)

You must still return a record for every id in holdout_ids — a missing (or extra) id is a structural violation and scores 0 — but only the sealed test tier counts toward the reward; the rest is a held-back dev tier you never see the composition of. So you cannot tune to the graded subset, and a solver that works on only part of the universe is not safe.

The metric is MAVE (lower is better); push it as low as you can. Only the submitted drivers are trusted — both V_hat and V* are recomputed by the grader's engine, so a reported value cannot be gamed.

Rollouts

75 minWall clock
$19.21Spend
22.9MTokens
21Versions, 15 kept

On the visible set

0.10 0.15 0.20 0.25 0.30 0 50 100 150 200 Agent step LOO median absolute valuation error ↓ v1 v2 v3 v4 v5 v6 v7 v8 v9 v10 v11 v12 v13 v14 v15 v16 v17 v18 v19 v20 v21
keptrolled backsubmitted
  1. v1The agent kept the inherited sector-reversion baseline as a checkpoint.0.28991 min · $0.24
  2. v2The agent predicted the valuation multiple directly instead of drivers.0.20737 min · $1.14
  3. v3The agent predicted enterprise value so net debt subtracts exactly.0.19999 min · $1.64
  4. v4The agent rebuilt a structured driver model, too weak on its own.0.213912 min · $2.17
  5. v5The agent blended the structured model with the direct multiple.0.188813 min · $2.36
  6. v6The agent isolated the long-run assumptions and fit them cleanly.0.170514 min · $2.68
  7. v7The agent gave each driver the history features that predict it.0.129221 min · $4.10
  8. v8The agent retuned shrinkage for stability rather than headline score.0.12424 min · $5.12
  9. v9The agent gave the thin consumer sector a simpler, safer model.0.122634 min · $8.41
  10. v10The agent diversified the consumer fallback with a second opinion.0.115435 min · $8.88
  11. v11The agent hedged unknown-sector names with a broad direct model.0.115437 min · $9.37
  12. v12The agent hedged names with no beta the same way.0.115440 min · $9.96
  13. v13The agent shrank terminal growth harder toward its sector.0.111146 min · $11.16
  14. v14The agent tried a rule for three-row histories, and it did not hold.0.105548 min · $11.65
  15. v15The agent let beta inform a minority share of terminal growth.0.106952 min · $12.47
  16. v16The agent tried dropping the direct-value hedge, and lost stability.0.105955 min · $13.36
  17. v17The agent tried smoothing reinvestment, and the DCF disliked it.0.104259 min · $14.22
  18. v18The agent tried self-supervised driver forecasts, and errors decoupled.0.137364 min · $15.57
  19. v19The agent tried forecasting cash margin directly, and the median fell.0.123866 min · $16.37
  20. v20The agent floored equity value so no name could come out negative.0.106967 min · $16.72
  21. v21The agent softened shrinkage on the beta part of terminal growth.0.105874 min · $18.60

On the hidden set

Original metricNormalised score
Starter0.2072310.0
Frontier-calibrated reference0.1234440.6
This run (GPT-5.6-sol)0.1402350.4798
50 minWall clock
$17.57Spend
21.0MTokens
9Versions, 7 kept

On the visible set

0.10 0.15 0.20 0.25 0.30 0 2 4 6 8 Agent step LOO median absolute valuation error ↓ v0 v1 v2 v3 v4 v5 v6 v7 v8
keptrolled backsubmitted
  1. v0The agent inherited the sector-reversion blend baseline0.289914 min · $3.67
  2. v1The agent rewrote the model as a panel age-profile growth fit0.192118 min · $5.29
  3. v2The agent imputed sector-peer quantities for names with a blank sector0.167718 min · $5.29
  4. v3The agent fit margin level and slope unweighted across all clean years0.15621 min · $6.24
  5. v4The agent fit the five horizon years jointly on a phi basis0.177229 min · $8.65
  6. v5The agent retargeted values with bootstrapped scenarios and lost to no retarget0.191233 min · $10.14
  7. v6The agent raised terminal-growth shrinkage and dropped the sector reinvest term0.164738 min · $12.04
  8. v7The agent added standardized ridge and shortened the growth halflife0.164745 min · $14.22
  9. v8The agent rescaled each margin path by a self-calibrating aim factor0.158349 min · $17.18

On the hidden set

Original metricNormalised score
Starter0.2072310.0
Frontier-calibrated reference0.1234440.6
This run (Opus 5)0.1234440.6000
38 minWall clock
$1.76Spend
7.9MTokens
3Versions, 2 kept

On the visible set

0.10 0.15 0.20 0.25 0.30 0 0 1 2 2 Agent step LOO median absolute valuation error ↓ v0 v1 v2
keptrolled backsubmitted
  1. v0The agent inherited the sector-reversion blend baseline0.2899
  2. v1The agent replaced it with exponential driver dynamics and CAPM-fitted WACC0.1349
  3. v2The agent added a net-debt leverage premium to the WACC ridge0.1325

On the hidden set

Original metricNormalised score
Starter0.2072310.0
Frontier-calibrated reference0.1234440.6
This run (Gemini 3.7 Flash)0.1592270.3438
420 minWall clock
$9.00Spend
18.9MTokens
7Versions, 6 kept

On the visible set

0.10 0.15 0.20 0.25 0.30 0 2 3 4 6 Agent step LOO median absolute valuation error ↓ v0 v1 v1.1 v2 v3 v4 v5
keptrolled backsubmitted
  1. v0The agent inherited the sector-reversion blend baseline0.2899$0.77
  2. v1The agent replaced the blend with per-target ridge on clean-history features0.2042$0.90
  3. v1.1The agent swapped sector dummies for horizon-specific sector-mean anchors0.1839$1.63
  4. v2The agent added a global unknown-sector anchor and two-stage terminal growth0.174$2.36
  5. v3The agent added EWMA state features and loosened the margin penalty0.1598$3.34
  6. v4The agent wrapped each company in a fallback to global-mean drivers0.1598$7.49
  7. v5The agent wrapped solve in an outer guard returning safe default drivers0.1598$8.56

On the hidden set

Original metricNormalised score
Starter0.2072310.0
Frontier-calibrated reference0.1234440.6
This run (Kimi K3)0.1487250.4190
51 minWall clock
$10.96Spend
14.3MTokens
4Versions, 3 kept

On the visible set

0.10 0.15 0.20 0.25 0.30 0 1 2 2 3 Agent step LOO median absolute valuation error ↓ v0 v1 v2 v3
keptrolled backsubmitted
  1. v0The agent inherited the sector-reversion blend baseline0.2899
  2. v1The agent rewrote it with sector-split growth fade and OLS beta WACC0.1702
  3. v2The agent lowered the margin own-weight schedule toward the OLS coefficient0.1549
  4. v3The agent added defensive clips on last-clean drivers and beta0.1549

On the hidden set

Original metricNormalised score
Starter0.2072310.0
Frontier-calibrated reference0.1234440.6
This run (Grok 4.6)0.1567530.3615
76 minWall clock
$1.41Spend
11.1MTokens
2Versions, 1 kept

On the visible set

0.10 0.15 0.20 0.25 0.30 0 20 40 60 80 Agent step LOO median absolute valuation error ↓ v1 v2
keptrolled backsubmitted
  1. v1The agent reverse-engineered the generator into per-driver ridge models0.263217 min · $0.48
  2. v2The agent replaced the margin ridge with a parametric decay and refit terminal growth0.176368 min · $1.18

On the hidden set

Original metricNormalised score
Starter0.2072310.0
Frontier-calibrated reference0.1234440.6
This run (DeepSeek V4 Pro)0.1527820.3899
57 minWall clock
$4.88Spend
14.7MTokens
13Versions, 5 kept

On the visible set

0.10 0.15 0.20 0.25 0.30 0 2 5 8 10 Agent step LOO median absolute valuation error ↓ v0 v1 v2 v3 v3a v4 v5 v6 v7 v8 v9 v10 FINAL
keptrolled backsubmitted
  1. v0The agent inherited the sector-reversion blend baseline0.28992 min · $0.15
  2. v1The agent built a structural generator with AR(1) growth and margin convergence0.21869 min · $0.64
  3. v2The agent rewrote it to match the generator with GLS-shrunk margin targets0.201618 min · $1.42
  4. v3The agent locked the core configuration and cleaned up the file0.185940 min · $2.96
  5. v3aThe agent added a residual-calibration layer and reverted it0.2455$3.12
  6. v4The agent tilted terminal growth by a company signal and reverted0.218$3.29
  7. v5The agent tried sector quantile anchors and flagged reinvestment rows0.2035$3.45
  8. v6The agent fit per-name growth persistence under a prior and reverted0.1859$3.61
  9. v7The agent averaged models over a phi-psi-tau grid and reverted0.1857$3.78
  10. v8The agent shrank small-sector anchors toward the panel and reverted0.2059$3.94
  11. v9The agent hardened fiscal-year fallbacks and ran a full grading simulation0.185952 min · $4.11
  12. v10The agent tried a nonparametric growth decay shape and reverted0.2067$4.49
  13. FINALThe agent submitted the hardened v9 configuration0.1859$4.49

On the hidden set

Original metricNormalised score
Starter0.2072310.0
Frontier-calibrated reference0.1234440.6
This run (Qwen3.8 Max)0.2044480.0199
76 minWall clock
$15.70Spend
54.0MTokens
9Versions, 8 kept

On the visible set

0.10 0.15 0.20 0.25 0.30 0 2 4 6 8 Agent step LOO median absolute valuation error ↓ v0 v2 v3 v4 v5 v6 v7 v8
keptrolled backsubmitted
  1. v0The agent inherited the sector-reversion blend baseline0.2899$0.32
  2. v1The agent examined the inherited own-history-trend sibling without grading itread, never run$0.63
  3. v2The agent built a structural model with geometric growth decay and sector margins0.193510 min · $0.95
  4. v3The agent fit the margin level from a least-squares line two years ahead0.175115 min · $1.63
  5. v4The agent eased growth decay by history length and retuned the weights0.162828 min · $3.42
  6. v5The agent treated blank sector as unknown and used posterior-weighted anchors0.144430 min · $3.76
  7. v6The agent retuned the configuration against a bootstrap-smoothed median objective0.129636 min · $4.85
  8. v7The agent blended own and sector growth paths year by year0.12548 min · $7.32
  9. v8The agent hardened NaN guards and unknown-id fallbacks0.121260 min · $10.53

On the hidden set

Original metricNormalised score
Starter0.2072310.0
Frontier-calibrated reference0.1234440.6
This run (GLM 5.3)0.1475920.4271
36 minWall clock
$6.54Spend
7.0MTokens
7Versions, 6 kept

On the visible set

0.10 0.15 0.20 0.25 0.30 0 20 40 60 80 Agent step LOO median absolute valuation error ↓ v0 v1 v2 v3 v4 v5 v6
keptrolled backsubmitted
  1. v0The agent inherited the sector-reversion blend baseline0.28991 min · $0.28
  2. v1The agent replaced the baseline with targeted ridge models per driver0.145711 min · $1.60
  3. v2The agent overrode blank sectors with the nearest observable sector centroid0.14116 min · $2.52
  4. v3The agent replaced the WACC ridge with a beta-plus-sector-offset fit0.138619 min · $3.40
  5. v4The agent retuned the operating-driver ridge penalties after the WACC change0.12827 min · $4.29
  6. v5The agent made the ridge penalties horizon-specific0.125929 min · $4.88
  7. v6The agent added a conservative value floor for negative predicted equity0.125934 min · $6.08

On the hidden set

Original metricNormalised score
Starter0.2072310.0
Frontier-calibrated reference0.1234440.6
This run (GPT-5.5)0.1554660.3707

Leaderboard

Where each run landed on the sealed held-out set, on the same normalised-score scale as the anchors above.

0 0.3 0.6 1.0 1 Opus 5 claude code · max 0.600 2 GPT-5.6-sol codex · max 0.480 3 GLM 5.3 claude code · max 0.427 4 Kimi K3 kimi cli · max 0.419 5 DeepSeek V4 Pro claude code · max 0.390 6 GPT-5.5 codex · xhigh 0.371 7 Grok 4.6 grok · xhigh 0.361 8 Gemini 3.7 Flash antigravity · high 0.344 9 Qwen3.8 Max qwen coder · xhigh 0.020