I'm considering investing multifamily properties in Raleigh NC. Give me a deep market entry analysis please cover MF fundamentals in detail (rent levels and growth by bedroom type, occupancy trends over time, new supply and pipeline, other demand drivers), how this metro compares to nearby competing metros, and a clear view on whether the timing and fundamentals support entering this market now
mkdir -p /vercel/sandbox/analysis && cat > /vercel/sandbox/analysis/raleigh_mf_calcs.py << 'EOF'
import json
# --- Raleigh rent by bedroom: Aug 2026 vs Aug 2025 (asking + in-place) ---
raleigh_beds = {
0: {"aug26_ask":1452.46,"aug25_ask":1403.73,"aug26_ip":1297.22,"aug25_ip":1306.16},
1: {"aug26_ask":1479.57,"aug25_ask":1473.23,"aug26_ip":1372.88,"aug25_ip":1380.81},
2: {"aug26_ask":1764.96,"aug25_ask":1802.27,"aug26_ip":1643.20,"aug25_ip":1651.64},
3: {"aug26_ask":2125.77,"aug25_ask":2096.47,"aug26_ip":1963.60,"aug25_ip":1962.39},
4: {"aug26_ask":2618.15,"aug25_ask":2362.02,"aug26_ip":2372.68,"aug25_ip":2272.39},
}
print("Raleigh MF rent growth by bedroom, Aug'25 -> Aug'26 (T12)")
for bed, d in raleigh_beds.items():
ask_g = (d["aug26_ask"]/d["aug25_ask"]-1)*100
ip_g = (d["aug26_ip"]/d["aug25_ip"]-1)*100
label = "Studio" if bed==0 else f"{bed}BR"
print(f"{label}: asking {d['aug26_ask']:.0f} ({ask_g:+.1f}% T12) | in-place {d['aug26_ip']:.0f} ({ip_g:+.1f}% T12)")
print()
# --- Raleigh overall asking/in-place spread + T12 rent growth (from snapshot) ---
raleigh_asking_latest = 1674.40
raleigh_inplace_latest = 1541.36
spread = (raleigh_asking_latest/raleigh_inplace_latest - 1)*100
print(f"Raleigh asking-vs-in-place spread: {spread:.1f}%")
print(f"Raleigh asking rent T12 chg: -1.54% (from snapshot); in-place T12 chg: -0.91%")
print()
# --- Market-level rent T12 growth (asking, median) using mf_rent_ts monthly Aug26 vs Aug25 ---
markets = {
"Raleigh": {"aug26_ask_med":1569.00, "aug25_ask_med":1560.00, "aug26_ip_med":1470.00,"aug25_ip_med":1478.00,
"aug26_occ":0.9398,"aug25_occ":0.9499, "unit_count":133137},
"Charlotte": {"aug26_ask_med":1615.00,"aug25_ask_med":1620.00,"aug26_ip_med":1516.00,"aug25_ip_med":1520.00,
"aug26_occ":0.9252,"aug25_occ":0.9559,"unit_count":220961},
"Durham": {"aug26_ask_med":1610.00,"aug25_ask_med":None,"aug26_ip_med":1480.00,"aug25_ip_med":None,
"aug26_occ":0.9242,"aug25_occ":None,"unit_count":59315},
"Nashville": {"aug26_ask_med":1695.00,"aug25_ask_med":None,"aug26_ip_med":1571.00,"aug25_ip_med":None,
"aug26_occ":0.9326,"aug25_occ":None,"unit_count":177268},
"Charleston": {"aug26_ask_med":1862.00,"aug25_ask_med":1807.50,"aug26_ip_med":1738.00,"aug25_ip_med":1723.00,
"aug26_occ":0.9540,"aug25_occ":0.9509,"unit_count":67673},
}
print("Market T12 asking-rent growth (median), occupancy delta (Aug25->Aug26):")
for m,d in markets.items():
if d["aug25_ask_med"]:
g = (d["aug26_ask_med"]/d["aug25_ask_med"]-1)*100
gs = f"{g:+.1f}%"
else:
gs = "n/a (need Sep'25 obs)"
occ_delta = None
if d["aug25_occ"]:
occ_delta = (d["aug26_occ"]-d["aug25_occ"])*100
print(f"{m}: asking rent T12 {gs}, occupancy {d['aug26_occ']*100:.1f}% (delta {occ_delta:+.1f}pp)" if occ_delta is not None else f"{m}: asking rent T12 {gs}, occupancy {d['aug26_occ']*100:.1f}%")
print()
# --- Supply metrics: UC % of stock, permits T12 % of stock, deliveries vs absorption ---
supply = {
"Raleigh": {"stock":133137,"uc":7342,"deliv_ytd":1425,"absorp":2204,"permit_t12":6857,"permit_t13_24":6246,"vac":0.095},
"Charlotte": {"stock":220961,"uc":16508,"deliv_ytd":9050,"absorp":5036,"permit_t12":6375,"permit_t13_24":6610,"vac":0.123},
"Durham": {"stock":59315,"uc":4282,"deliv_ytd":1185,"absorp":258,"permit_t12":2724,"permit_t13_24":1919,"vac":0.120},
"Nashville": {"stock":177268,"uc":11714,"deliv_ytd":3124,"absorp":2911,"permit_t12":4535,"permit_t13_24":5350,"vac":0.103},
"Charleston": {"stock":67673,"uc":3080,"deliv_ytd":930,"absorp":1021,"permit_t12":1715,"permit_t13_24":1805,"vac":0.084},
}
print("Supply pipeline pressure (market grain, Cushman & Wakefield / Census BPS):")
for m,d in supply.items():
uc_pct = d["uc"]/d["stock"]*100
permit_pct = d["permit_t12"]/d["stock"]*100
permit_trend = (d["permit_t12"]/d["permit_t13_24"]-1)*100
print(f"{m}: UC {d['uc']:,} = {uc_pct:.1f}% of {d['stock']:,} stock | permits T12 {d['permit_t12']:,} ({permit_pct:.1f}% of stock, {permit_trend:+.0f}% vs T13-24) | vacancy {d['vac']*100:.1f}% | YTD deliveries {d['deliv_ytd']:,} vs absorption {d['absorp']:,}")
print()
# --- Cap rate trend: 3Q22 trough to 2Q26 for Raleigh, Charlotte, Nashville ---
caprates = {
"Raleigh": {"3Q22":0.0425,"4Q22":0.0501,"2Q24":0.0521,"2Q25":0.0507,"2Q26":0.0507},
"Charlotte": {"3Q22":0.0425,"4Q22":0.0501,"2Q24":0.0528,"2Q25":0.0513,"2Q26":0.0514},
"Nashville": {"3Q22":0.0399,"4Q22":0.0475,"2Q24":0.0498,"2Q25":0.0517,"2Q26":0.0518},
}
print("MF cap rate trajectory (trough 3Q22 -> latest 2Q26), bps expansion:")
for m,d in caprates.items():
exp_bps = (d["2Q26"]-d["3Q22"])*10000
recent_chg = (d["2Q26"]-d["2Q25"])*10000
print(f"{m}: 3Q22 {d['3Q22']*100:.2f}% -> 2Q26 {d['2Q26']*100:.2f}% ({exp_bps:+.0f}bps since trough); last 12mo change {recent_chg:+.0f}bps")
print()
# --- Migration net pct as % pop, income delta ---
migr = {
"Raleigh": {"net_pct":0.006028,"inc_diff":-1434,"educ_in":7.5,"educ_out":7.4,"nw_in":4.9,"nw_out":6.2},
"Charlotte": {"net_pct":0.004051,"inc_diff":-687,"educ_in":7.2,"educ_out":6.9,"nw_in":4.3,"nw_out":6.1},
"Durham": {"net_pct":-0.000627,"inc_diff":8781,"educ_in":7.8,"educ_out":7.6,"nw_in":5.0,"nw_out":5.8},
"Nashville": {"net_pct":0.007914,"inc_diff":1726,"educ_in":7.1,"educ_out":6.9,"nw_in":5.1,"nw_out":6.7},
"Charleston": {"net_pct":0.0113,"inc_diff":2530,"educ_in":7.4,"educ_out":7.1,"nw_in":4.6,"nw_out":5.8},
}
print("Net migration as % of population, and inbound-outbound income delta:")
for m,d in migr.items():
print(f"{m}: net migration {d['net_pct']*100:.2f}% of pop | inbound-outbound income delta ${d['inc_diff']:,} | education in {d['educ_in']} vs out {d['educ_out']}")
print()
# rent-to-income and job/pop growth quick recap
rti = {"Raleigh":0.2479,"Charlotte":0.2934,"Durham":0.2991,"Nashville":0.3059,"Charleston":0.3233}
pop_growth = {"Raleigh":0.035,"Charlotte":0.0279,"Durham":0.0191,"Nashville":0.0226,"Charleston":0.0242}
job_growth = {"Raleigh":0.0443,"Charlotte":0.0399,"Durham":-0.0047,"Nashville":0.0324,"Charleston":0.0254}
print("Rent-to-income, pop growth (T12), job growth (T12):")
for m in rti:
print(f"{m}: RTI {rti[m]*100:.1f}%, pop growth {pop_growth[m]*100:.1f}%, job growth {job_growth[m]*100:.1f}%")
EOF
python3 /vercel/sandbox/analysis/raleigh_mf_calcs.pyRaleigh's demand story is the best of this Southeast peer set, but the entry window isn't clean today — a still-swelling supply pipeline and 15 straight months of falling occupancy argue for underwriting through 2027 rather than buying stabilized product now.
| Metric | |||||
|---|---|---|---|---|---|
| Demand / Migration | |||||
| Net Migration (% of Population) | 0.60% | 0.41% | -0.06% | 0.79% | 1.13% |
| Verdict | Inflow | Inflow | Outflow | Strong Inflow | Strong Inflow |
| Cohort Quality | |||||
| Inbound–Outbound Income Delta ($/yr) | -$1,434 | -$687 | +$8,781 | +$1,726 | +$2,530 |
| Verdict | Stable | Stable | Upgrading | Stable | Upgrading |
| Rent Trajectory | |||||
| T12 Asking Rent Change (%) | -1.54% | -0.23% | -0.50% | +3.04% | +3.65% |
| Occupancy Change, T12 (pts) | -2.06 | -4.12 | -4.87 | -3.64 | -0.30 |
| Verdict | Softening | Softening | Softening | Mixed | Strengthening |
| Supply Pipeline | |||||
| Under Construction (% of Existing Stock)1 | 3.36% | 4.31% | 4.28% | 3.81% | 2.88% |
| Permit Trend, T12 vs T13–24 (%) | +9.8% | -3.6% | +41.9% | -15.2% | -5.0% |
| Verdict | Balanced | Heavy | Heavy | Balanced | Constrained |
| Pricing / Cap Rate | |||||
| Latest MSA Multifamily Cap Rate | 5.07% | 5.14% | —2 | 5.18% | —2 |
| Change Since 3Q22 Trough (bps) | +82 bps | +89 bps | —2 | +119 bps | —2 |
| Verdict | Tight | Fair | N/A2 | Wide | N/A2 |
| Affordability | |||||
| Rent-to-Income Ratio | 24.8% | 29.3% | 29.9% | 30.6% | 32.3% |
| Verdict | Room to Run | Moderate | Moderate | Moderate | Stretched |
Verdict: For a stabilized-MF entry, Raleigh ranks first on cohort and affordability but is mid-pack on timing — occupancy has slid ~140bps over the past 20 months and under-construction units still equal 5.5% of stock, so the buy window opens once absorption catches the pipeline, likely 2027. The one number worth tracking before committing capital: Raleigh's UC-to-stock ratio and permit trend over the next 2-3 quarters — a deceleration there is the confirming signal, not the migration data, which already looks fine.
What stands out:
Rents by bedroom type. Raleigh's aggregate rent looks flat (asking rent -1.5% T12, in-place -0.9%) but that hides real divergence by unit type: studios are up 3.5% and 4-bedrooms are up a striking 10.8% on asking (4.4% on in-place, though on a thin sample of ~80 units), while the core 2-bedroom segment — the largest pool by leases signed — is down 2.1% on asking . The 1BR and 3BR segments are roughly flat. That's a market where large-format and studio product is absorbing pricing power while the bread-and-butter 2BR is still digesting supply.
Asking-vs-in-place spread and leasing velocity. Asking rents sit 8.6% above in-place rents — a wide loss-to-lease gap — but the near-zero tradeout (+0.05%) says landlords aren't actually capturing that gap on turnover; new leases are signing close to what the outgoing tenant paid, not at the posted asking rate . Days-on-market of 66-79 days across recent months confirms concessions and slower lease-up, not a landlord's market.
Occupancy has been sliding for 15+ months. Raleigh physical occupancy fell from 95.4% in January 2025 to 94.0% in August 2026 — about 140bps of continuous erosion — as roughly 7,300 units delivered into the market against 2,200 units of net absorption year-to-date . That absorption-to-delivery ratio (about 1:0.65) is the single clearest signal that supply is still outrunning demand.
Supply pipeline is the binding constraint. 7,342 units under construction equal 5.5% of Raleigh's 133,137-unit stock, and trailing-12-month permits (6,857) are running 10% ahead of the prior 12 months — the pipeline is still growing, not rolling off . That's a materially different signal than Charlotte or Nashville, where permits have decelerated 4-15% even though their under-construction ratios are higher in absolute terms (7.5% and 6.6% of stock, respectively) — those two markets are past their permit peak; Raleigh isn't yet.
Other demand drivers. Raleigh's job growth (4.4% T12) and population growth (3.5% T12) both lead the peer set, and its median household income ($116K) and education profile (96th national percentile on the education score, 68% bachelor's+) are the strongest of the five . The one soft spot: migration cohort quality is mildly downgrading — households moving out of Raleigh earn about $1,400 more than households moving in, and the outbound cohort carries a meaningfully higher net-worth tier (6.2 vs. 4.9 inbound) . That's not alarming on its own (Charlotte shows the same pattern more sharply, at -$687 and a wider net-worth gap), but it means Raleigh's rent-growth thesis rests more on job and population volume than on an upgrading tenant base.
Charleston is the standout on near-term pricing power — it's the only market with occupancy rising and tradeouts positive, though it's also the most rent-burdened (32.3% RTI) and geographically the least comparable (smaller, coastal, tourism-inflected economy). Nashville and Charlotte both show cap rates still drifting wider (+119bps and +89bps off the 2022 trough, vs. Raleigh's +82bps) alongside occupancy down 300+bps, suggesting their supply hangovers are further along in severity but not yet in recovery. Durham — Raleigh's closest neighbor and often bundled into the same MSA narrative — is actually the weakest read here: flat-to-negative net migration, the highest permit acceleration in the set (+42%), and the softest job growth (-0.5% T12), even though its migration income delta looks strong (thin-sample caveat: Durham's inbound/outbound migration pool is smaller and more sensitive to a handful of moves).
Act on now: hold off on stabilized MF acquisition in Raleigh until the UC-to-stock ratio and T12 permit trend both show deceleration — the demand side (jobs, income, education) is already strong enough to support entry the moment supply confirms it's turning. What would flip this: two consecutive quarters of permits declining vs. the prior-12-month pace, which would signal the pipeline has crested.
This is a read-only snapshot of a RealAI analysis.