Tax-Loss Harvesting Deadlines: The Real Dates
Tax-loss harvesting deadlines, in order: the last trading session of the year, the 61 day wash sale window, and the purchase dates that quietly reopen it.
Tax-loss harvesting deadlines come in pairs, and the second one is the one that bites. A sale that books a loss in a given tax year has to be executed on or before the last regular trading session of that calendar year. The wash sale rule then reaches 30 days backward and 30 days forward from the sale date, which puts a late-December harvest under constraint from purchases going back into November and forward into late January. Everything below is US federal mechanics, current as of October 2026, and none of it is tax advice.
Which tax-loss harvesting deadlines actually bind
Four dates govern a single harvest, and only one of them is the famous one.
- The execution date of the sale. For stock and ETF shares, the trade date controls the tax year.
- The settlement date, one business day later under T+1 settlement. It never moves the loss into another tax year, and it is the practical cutoff for telling a broker which tax lot was sold.
- The 30 calendar days before the sale. A purchase of substantially identical stock inside that stretch counts as a replacement purchase.
- The 30 calendar days after the sale, under the same test.
A December 31 execution that settles in January is still a December loss. The wash sale window, by contrast, cares nothing about the tax year boundary. It runs straight through it.
When is the last day to sell for a tax loss?
The last regular session of the calendar year, whichever weekday that turns out to be. The exchange calendar decides it, and the back end of December carries the year's densest cluster of shortened sessions. The panel below counts regular-hours one-minute bars in SPY, the S&P 500 ETF, across the final stretch of December 2025. A full session prints 390 of them, which is six and a half hours at sixty bars an hour.
| session_date | session_label | weekday | regular_minutes | full_session_pct |
|---|---|---|---|---|
| 2025-12-15 | Dec 15, 2025 | Mon | 390 | 100 |
| 2025-12-16 | Dec 16, 2025 | Tue | 390 | 100 |
| 2025-12-17 | Dec 17, 2025 | Wed | 390 | 100 |
| 2025-12-18 | Dec 18, 2025 | Thu | 390 | 100 |
| 2025-12-19 | Dec 19, 2025 | Fri | 390 | 100 |
| 2025-12-22 | Dec 22, 2025 | Mon | 390 | 100 |
| 2025-12-23 | Dec 23, 2025 | Tue | 390 | 100 |
| 2025-12-24 | Dec 24, 2025 | Wed | 211 | 54.1 |
| 2025-12-26 | Dec 26, 2025 | Fri | 390 | 100 |
| 2025-12-29 | Dec 29, 2025 | Mon | 390 | 100 |
| 2025-12-30 | Dec 30, 2025 | Tue | 390 | 100 |
| 2025-12-31 | Dec 31, 2025 | Wed | 390 | 100 |
The exact SQL behind every number
SELECT
toString(d) AS session_date,
formatDateTime(d, '%b %e, %Y') AS session_label,
formatDateTime(d, '%a') AS weekday,
toUInt32(bars) AS regular_minutes,
round(toFloat64(bars) / 390 * 100, 1) AS full_session_pct
FROM
(
SELECT
toDate(toTimeZone(window_start, 'America/New_York')) AS d,
count() AS bars
FROM global_markets.delayed_stocks_minute_aggs
WHERE ticker = 'SPY'
AND window_start >= '2025-12-14 00:00:00'
AND window_start < '2026-01-01 00:00:00'
AND (toHour(toTimeZone(window_start, 'America/New_York')) * 60
+ toMinute(toTimeZone(window_start, 'America/New_York'))) >= 570
AND (toHour(toTimeZone(window_start, 'America/New_York')) * 60
+ toMinute(toTimeZone(window_start, 'America/New_York'))) < 960
GROUP BY d
)
ORDER BY dThe final session of that year, Dec 31, 2025, printed 390 regular-session minutes, a full day. One session in the same stretch, Dec 24, 2025, printed 211, which is 54.1% of a normal day. That is the 1:00 p.m. ET Christmas Eve close. In a year where an early close lands on the final session, a harvest planned against a 4:00 p.m. close loses three hours of liquidity. Trades printed in the extended session after the regular close still carry that day's trade date, though the thin book there is its own problem.
How long is the wash sale window?
Sixty-one calendar days: the 30 before the sale, the sale date itself, and the 30 after. The count is in calendar days, not sessions, which is why the window reads more plainly once it is converted into the sessions it actually spans.
| sale_date | sale_label | window_opens | window_closes | sessions_before | sessions_after |
|---|---|---|---|---|---|
| 2025-12-22 | Dec 22, 2025 | Nov 22, 2025 | Jan 21, 2026 | 19 | 19 |
| 2025-12-23 | Dec 23, 2025 | Nov 23, 2025 | Jan 22, 2026 | 20 | 19 |
| 2025-12-24 | Dec 24, 2025 | Nov 24, 2025 | Jan 23, 2026 | 21 | 19 |
| 2025-12-26 | Dec 26, 2025 | Nov 26, 2025 | Jan 25, 2026 | 20 | 18 |
| 2025-12-29 | Dec 29, 2025 | Nov 29, 2025 | Jan 28, 2026 | 19 | 20 |
| 2025-12-30 | Dec 30, 2025 | Nov 30, 2025 | Jan 29, 2026 | 20 | 20 |
| 2025-12-31 | Dec 31, 2025 | Dec 1, 2025 | Jan 30, 2026 | 21 | 20 |
The exact SQL behind every number
WITH sessions AS
(
SELECT DISTINCT toDate(toTimeZone(window_start, 'America/New_York')) AS d
FROM global_markets.delayed_stocks_minute_aggs
WHERE ticker = 'SPY'
AND window_start >= '2025-11-18 00:00:00'
AND window_start < '2026-02-02 00:00:00'
AND (toHour(toTimeZone(window_start, 'America/New_York')) * 60
+ toMinute(toTimeZone(window_start, 'America/New_York'))) >= 570
AND (toHour(toTimeZone(window_start, 'America/New_York')) * 60
+ toMinute(toTimeZone(window_start, 'America/New_York'))) < 960
)
SELECT
toString(s.d) AS sale_date,
formatDateTime(s.d, '%b %e, %Y') AS sale_label,
formatDateTime(s.d - 30, '%b %e, %Y') AS window_opens,
formatDateTime(s.d + 30, '%b %e, %Y') AS window_closes,
countIf(t.d >= s.d - 30 AND t.d < s.d) AS sessions_before,
countIf(t.d > s.d AND t.d <= s.d + 30) AS sessions_after
FROM
(
SELECT d
FROM sessions
WHERE d >= '2025-12-22'
AND d < '2026-01-01'
) AS s
CROSS JOIN sessions AS t
GROUP BY s.d
ORDER BY s.dTake the last row, the final session of the year. A loss sale on Dec 31, 2025 opens a window on Dec 1, 2025 and closes it on Jan 30, 2026, covering 21 trading sessions ahead of the sale and 20 after it. A purchase of substantially identical stock anywhere inside that span disallows the loss in proportion to the shares replaced.
Does buying before the sale count?
Yes, and that is the half nobody spells out. The provision covers purchases within 30 days before the sale in the same terms as purchases within 30 days after. Adding to a position in early December and then selling the older, losing lot on the year's last session is a wash sale in exactly the way that selling first and buying back a week later is. Order of operations is irrelevant to the rule.
The doubling-up sequence that clears the test runs on a 31 day clock: buy the extra shares, let more than 30 days pass, then sell the old lot. A harvest on the final session of the year means that buy leg had to land in late November at the latest.
When the rule does apply to stock, the loss is deferred rather than destroyed. The disallowed amount is added to the basis of the replacement shares, and the holding period of the sold shares is tacked onto them. Which lot was sold, and which lot carries that adjusted basis, is a cost-basis election made at the sale; see FIFO versus specific lot identification. Options sit on top of all this, since a call you bought or a put you wrote on the same name can be the replacement property. The wash sale rule and options handles that case on its own.
How losses net, and what the $3,000 limit does
The arithmetic after the sale runs in a fixed order. Short-term losses net against short-term gains and long-term against long-term, then the two net figures offset each other. If a net capital loss remains, up to $3,000 of it offsets ordinary income for the year, or $1,500 for a married taxpayer filing separately. The remainder carries forward indefinitely for an individual and keeps its short or long character. Those dollar figures are statutory and current as of October 2026. A number Congress sets is a number Congress can change.
One consequence of that ordering is worth stating plainly: a loss harvested in a year with large realized gains is absorbed at the full size of those gains, while the same loss in a year with none drains at $3,000 a year.
The purchases that reopen the window
The trap is rarely a deliberate repurchase. It is a standing instruction. Dividend reinvestment buys shares on the pay date, and the pay date can sit weeks after the ex-dividend date it belongs to.
| ticker | ex_date_label | pay_date_label | days_ex_to_pay |
|---|---|---|---|
| SPY | Dec 19, 2025 | Jan 30, 2026 | 42 |
| PEP | Dec 5, 2025 | Jan 6, 2026 | 32 |
| XOM | Nov 14, 2025 | Dec 10, 2025 | 26 |
| MSFT | Nov 20, 2025 | Dec 11, 2025 | 21 |
| HD | Dec 4, 2025 | Dec 18, 2025 | 14 |
| JNJ | Nov 25, 2025 | Dec 9, 2025 | 14 |
| KO | Dec 1, 2025 | Dec 15, 2025 | 14 |
| MCD | Dec 1, 2025 | Dec 15, 2025 | 14 |
| IVV | Dec 16, 2025 | Dec 19, 2025 | 3 |
| VOO | Dec 22, 2025 | Dec 24, 2025 | 2 |
The exact SQL behind every number
SELECT
ticker,
formatDateTime(ex_dividend_date, '%b %e, %Y') AS ex_date_label,
formatDateTime(pay_date, '%b %e, %Y') AS pay_date_label,
dateDiff('day', ex_dividend_date, pay_date) AS days_ex_to_pay
FROM global_markets.stocks_dividends
WHERE ticker IN ('SPY', 'IVV', 'VOO', 'KO', 'JNJ', 'PG', 'MSFT', 'XOM', 'VZ', 'MCD', 'PEP', 'HD')
AND pay_date >= '2025-12-01'
AND pay_date <= '2026-01-31'
GROUP BY ticker, ex_dividend_date, pay_date
ORDER BY days_ex_to_pay DESC, ticker10 of those household payers had a distribution payable between early December 2025 and the end of January 2026. The widest lag belongs to SPY, at 42 days from its ex-dividend date to a pay date of Jan 30, 2026. A holder with reinvestment switched on collects new shares on that pay date, deep inside the forward half of a year-end window, usually without opening a statement.
A fractional share counts. The rule carries no de minimis floor, and a partial replacement disallows the loss only on the shares replaced, which leaves a position holding two lots with different basis treatment. Automatic monthly contributions behave the same way, as does a purchase in a spouse's account.
The retirement-account version is the one that ends badly. Under Rev. Rul. 2008-5, published in 2008, selling at a loss in a taxable account and buying substantially identical stock in your own IRA or Roth IRA inside the window disallows the loss, and the IRA's basis in the new shares is not increased. Nothing is deferred there. The deduction is gone for good. Funds carry extra timing wrinkles on the sale side, covered in selling mutual funds at a loss.
What does substantially identical mean?
The phrase sits in the statute and is nowhere defined for funds. The settled ground is narrow. A security is substantially identical to itself, and bonds or preferred shares with materially different terms have long been treated as distinct. Two index funds from different sponsors tracking one index sit in open country, with no ruling and no safe harbor as of October 2026. What can be measured is how closely they move. The panel compares each fund's open-to-close percent move against SPY's on the same session, in basis points, over the second half of 2025.
| ticker | median_gap_bps | max_gap_bps |
|---|---|---|
| VOO | 0.8 | 6.7 |
| IVV | 0.8 | 4 |
| QQQ | 16.6 | 121.4 |
| RSP | 27.9 | 120.8 |
The exact SQL behind every number
WITH moves AS
(
SELECT
ticker,
date,
toFloat64(close) / toFloat64(open) - 1 AS pct_move
FROM global_markets.stocks_daily_aggs
WHERE ticker IN ('SPY', 'IVV', 'VOO', 'RSP', 'QQQ')
AND date >= '2025-07-01'
AND date <= '2025-12-31'
AND open > 0
)
SELECT
f.ticker AS ticker,
round(quantileDeterministic(0.5)(abs(f.pct_move - b.spy_move) * 10000, toUInt64(f.date)), 1) AS median_gap_bps,
round(max(abs(f.pct_move - b.spy_move)) * 10000, 1) AS max_gap_bps
FROM moves AS f
INNER JOIN
(
SELECT date, pct_move AS spy_move
FROM moves
WHERE ticker = 'SPY'
) AS b USING (date)
WHERE f.ticker != 'SPY'
GROUP BY f.ticker
ORDER BY median_gap_bpsVOO and IVV sit at the tight end, a median 0.8 and 0.8 basis points a session away from SPY, with worst sessions of 6.7 and 4 basis points. Both track the same index SPY tracks. RSP, built on a different index, sits a median 27.9 basis points away. A gap of a basis point or two is a price measurement, not a legal test, and no authority has said where on this scale the line falls. The cautious reading treats same-index funds from different issuers as live risk. The aggressive reading leans on the separate sponsor and separate prospectus.
FAQ
What is the last day to sell stock for a tax loss?
The last regular trading session of the calendar year. Trade date controls for stock and ETFs, so an execution on that session books the loss in that year even though it settles the next business day. The exchange calendar sets the exact date, and that session is sometimes a shortened one.
Does the wash sale rule count calendar days or trading days?
Calendar days. Thirty before the sale, thirty after, plus the sale date itself, for 61 days in total. Weekends and holidays fall inside the count, which is why the 30 days before a year-end sale cover only 21 trading sessions.
Does buying shares before the sale count as a wash sale?
Yes. A purchase of substantially identical stock in the 30 days before the loss sale is a replacement purchase under the same provision that covers a purchase after it. Buying first and selling the old lot second is caught exactly as the reverse order is.
Can I sell at a loss and buy it back inside my IRA?
Rev. Rul. 2008-5 disallows the loss, and the IRA's basis in the new shares is not increased. Unlike an ordinary wash sale in a taxable account, that loss is permanently lost rather than deferred.
How much capital loss can offset ordinary income?
Up to $3,000 a year for most filers, or $1,500 for a married taxpayer filing separately, after losses have netted against gains by character. Anything left carries forward with no expiration. Both figures are current as of October 2026.
Every panel here ships with the exact SQL underneath it. To count the trading sessions inside a 61 day window around any date, or find which session closed a given year, ask it in plain English on the Strasmore terminal.