How the detection engine works
Last updated 2026-10-05 · applies to the current version of the checker
A tax tool earns trust by showing its work. This page documents every rule the engine applies and — just as important — every situation it deliberately refuses to judge. Our design principle: a wrong "you're safe" is the worst possible output, so when in doubt the engine flags, warns, or abstains. It never silently guesses.
The rules we apply
- The 61-day window, inclusive. A loss sale on day D is washed by a substantially identical acquisition in any of your accounts from D−30 through D+30 (IRC §1091(a)). Both boundary days count. Trade date governs, not settlement date.
- Cross-account, cross-broker aggregation. All accounts you load are treated as one taxpayer, matching IRS treatment. Tag each account taxable or IRA — the distinction changes outcomes. A file that lists several broker accounts (Fidelity's all-accounts download) is split: each account's trades go into the account you choose for it, and the choice is remembered for later files whose rows name that account.
- FIFO lot matching. Sells relieve the earliest-acquired lot first. If you elected specific-lot identification at your broker, your realized amounts may differ (a stated limitation).
- Replacement shares: earliest-acquired first, each share used at most once (per IRS Pub 550's matching order). Partial quantities split proportionally, including fractional shares.
- Shares kept from the same purchase. When you sell part of one purchase (one imported buy trade) at a loss, the shares or contracts you keep from that purchase are never counted as replacements for that loss, because the wash-sale rule doesn't reach a bona fide sale that reduces your holdings (Rev. Rul. 56-602, 1956-2 C.B. 527); any other purchase in the 61-day window, even one made the same day, still is. When that other purchase is of the same security, in the same account and on the same day as the purchase the sold shares came from, and apart from such same-day purchases nothing else bought or acquired within 30 days before or after the sale could replace the loss (and no put on that stock was written in that time), the two rows may be fills of one order (Robinhood can fill one dollar-amount order as two rows); the file doesn't show orders, so the loss still counts as washed, and the violation and the rows it touches carry a CPA check.
- Basis adjustment and holding-period tacking. A disallowed loss is added to the replacement lot's basis and the original holding period tacks on — so deferred losses are recovered on a later clean sale, and cascading washes chain correctly.
- Holding period. A sale is long-term when the shares were held more than one year, counted from the day after the purchase. For a purchase on a month's last day the checker follows Rev. Rul. 66-7: long-term from the first day of the 13th month after, so shares bought on February 29 are long-term from March 1 of the next year, and shares bought on February 28 the year before a leap year are still short-term on February 29. A row on one of those two days carries a CPA check naming both readings. A holding period carried over from a washed loss is counted in days; when that puts a sale within 3 days of the one-year line, the row carries a CPA check too.
- IRA absorption is permanent. When the replacement lands in a traditional or Roth IRA, the loss is flagged as permanently disallowed with no basis adjustment anywhere, per Rev. Rul. 2008-5. This is displayed in its own unmissable category.
- Substantially-identical tiers (conservative by design): (1) same ticker (class-share spellings like BRK/B, BRK.B, BRK B, BRK-B, and the option root BRKB count as the same ticker), or the exact same option contract → full dollar math. (2) Stock loss followed by a call purchase on the same underlying → statutory violation (IRC §1091(a) "contract or option to acquire"). The disallowed loss is added to the call's basis and the stock's holding period tacks on (§1091(d)); when the loss covers fewer than 100 shares, the adjustment lands on the matching fraction of a contract. If that call later expires, its $0 expiration sale realizes the premium plus the carried-over loss. If the call was bought in an IRA, the loss is permanently disallowed instead. Confirm the option treatment with a CPA. (3) Any other same-underlying acquisition (different strike/expiry, puts) → a caution flag with no dollar math — possibly substantially identical, review with a professional. Tier 3 is never marked "safe."
- Year attribution. A December loss washed by a January re-buy is disallowed in the December tax year, matching 1099-B treatment.
- Index options are left out (Section 1256). Options on a broad-based stock index are Section 1256 contracts (IRC §1256): they're reported on Form 6781, their gains and losses count as 60% long-term and 40% short-term however long you held them, and any still open at year end are treated as sold at fair market value on the last business day of the year. The wash-sale rule doesn't apply to them (Form 6781 instructions). So trades in SPX/SPXW, XSP, NANOS, NDX/NDXP, NQX, XND, RUT/RUTW, MRUT, RUI, RLG, RLV, OEX, XEO and DJX/DJXW options are left out of the wash-sale checks (neither as a loss nor as a replacement) and out of the year-end summary, and a notice names them with their trade counts. That notice doesn't withhold anything else. The list is deliberately short: for most of these roots, the exchange that lists them states on its own product material that they get 60/40 Section 1256 treatment (Cboe for SPX, XSP, NANOS, RUT, MRUT, RUI, RLG, RLV, OEX, XEO and DJX; Nasdaq for NDX, XND and NQX). SPXW, NDXP, RUTW and DJXW are other series of the same exchange's SPX, NDX, RUT and DJX index options. Options on ETFs and ETNs, such as SPY, QQQ, IWM, DIA, VXX or UVXY, are equity options and are checked like any other option.
- VIX options are withheld, not guessed. Whether options on the Cboe Volatility Index (VIX and its weekly series, VIXW) are Section 1256 contracts is unclear: some brokers, tastytrade among them, list them with the broad-based index options that get Section 1256 treatment, but Cboe's own VIX options page makes no 60/40 statement. If they are, the wash-sale rule doesn't apply; if they aren't, it does. So VIX and VIXW option trades are left out of the checks and the year-end summary, and every verdict for them is withheld with a notice that says why. Check your 1099-B: a broker that treats them as Section 1256 contracts reports them in boxes 8 through 11.
When the engine refuses to answer
When a verdict is withheld, the symbol drops out of the danger windows, the violations and the year-end totals, and a notice names it instead. The checker never shows an all-clear while any symbol is withheld, or while a symbol needs a CPA check because of options you wrote or an option assignment or exercise (see “Options you wrote” and “Assignment and exercise” below); the dashboard names those symbols instead.
- Incomplete history. If your sells exceed your uploaded purchase history for a symbol, every verdict touching that symbol (shares AND options on it) is suppressed and replaced with an explicit "incomplete history" notice. Upload more history to unlock it.
- Holdings check. Positions bought before your export starts don't appear in its rows. For each account, the dashboard shows what the imported rows say it held (shares, and options bought or written that haven't expired by then) at the end of the last day with a trade or another row the checker uses, such as an option expiration or a share transfer; rows it doesn't use, such as deposits and interest, don't count. It names the securities the account traded but ended with no position in (no shares, and no options on them; a security with a row it doesn't apply, below, isn't among them), and asks whether that's everything you held, with nothing missing. Options on broad-based indexes and on VIX aren't asked about. When the account has a row on a security that the checker doesn't apply (such as a split or a transfer), its positions in that security (shares and options on it) are shown but not asked about, since that row may change them, and that security's verdicts are already withheld. If you say a position was different, or add one the rows don't show, every verdict and year-end row touching that security (shares and options on it) is withheld whenever a taxable account has trades in it, with a notice naming the account, what the rows show and what you said; a security traded only in IRAs is recorded but withholds nothing, since no taxable result depends on it. Saying yes changes nothing. Your answers stay in this browser with your ledger. When an account's rows change the positions it asks about, the securities it names as traded, or the date, it's asked again, and a difference you reported keeps withholding until you answer again. An account you haven't checked withholds nothing, and the Form 8949 worksheet says for each account whether you confirmed its holdings as complete, and when.
- Dividends paid on more shares than the file holds. Robinhood's dividend rows say how many shares a dividend was paid on. If that's more than your imported trades hold in that account on every day from a week before the dividend's record date through the record date, your history most likely starts too late, and every verdict and year-end row touching that stock (shares and options on it) is withheld whenever a taxable account has trades in it. The dividend rows we've seen from other brokers don't state a share count, so this check covers Robinhood only. When a file gives no record date, the pay date is used, which can make the check later than the real record date.
- Splits, mergers, spin-offs, share transfers, stock-plan deposits and unrecognized share movements. These rows change share counts outside the trades, and we don't rebuild lots from them. Every verdict and year-end row for that symbol (shares and options on it) is withheld. The notice quotes the broker's own wording with the date and account, so you know what to check against your broker's cost-basis page or 1099-B. A dividend-reinvestment row we couldn't read as a purchase is treated the same way. These events are saved with your ledger, so the notice survives a reload or a backup restore.
- Adjusted option contracts. After a corporate action, OCC renames an option's root (GME to GME1) and changes what it delivers. Any trade or option row on such a contract withholds both the root and the stock (shares and options on them), since we don't model the new deliverable.
- Short sales. A short sale or cover the broker labels (sell short, buy to cover)
withholds every verdict for that symbol, because a cover interacts with any long shares in ways the
engine doesn't model. Options you write (covered calls, cash-secured puts) aren't short sales of stock
and don't withhold anything; they're covered under “Options you wrote” below. An unlabelled sell that
takes a
position below zero, followed by a buy while it's still negative, is treated as a probable short sale
(or missing history) and withheld. What we can't detect, when the broker file doesn't label
the short sale:
- A short against the box: shorting a stock you also hold long in the same account. It looks exactly like an ordinary sale and re-buy. If the box is still open at the end of the file, we book a loss that hasn't happened yet, possibly in the wrong tax year.
- A short sale opened and covered on the same day from a flat position. Within a day we process buys before sells, so the position never looks negative, and wash-sale results can be wrong in both directions.
- A cover of a short sale opened before the file starts. It looks like an ordinary purchase, and the later sale's gain or loss can even come out with the wrong sign.
In a column-mapped file, the checker recognizes a short sale or an opening/closing leg only by its Buy/Sell value. It must be exactly SS, SSHORT, SHRT, BC, BTC, BTO, STO or STC, or use whole words: SHORT or COVER on its own or next to a trade word such as SELL, BUY, TO or SALE (SELL SHORT, Sell-Short, BUY TO COVER), or TO OPEN / TO CLOSE at the start or end of the value (SELL_TO_OPEN, Buy to Close). A fund name such as "SHORT TREASURY BOND ETF", "YOU BOUGHT SHORT TERM TREASURY" or "Short-term reserves" isn't read as a label. Rows with a recognized value that isn't in your buy or sell words are withheld and named. So are rows whose value sits in the list its own words contradict: one that says SELL, SOLD or SELLING, or SHORT without COVER, in the buy words; one that says BUY, BOUGHT or BUYING, or COVER without SELL or SOLD, in the sell words; or an abbreviation on the wrong side, such as SS in the buy words or BTC in the sell words. Put values that sell or short in the sell words and values that buy or cover in the buy words, and they're handled as short-sale or opening/closing legs. Don't delete them. Any other value in your buy or sell words is read as a plain buy or sell, so a short sale written as a plain buy or sell, or labelled any other way and put in those words, can't be detected. A value in neither list is withheld when its row names a symbol and a non-zero quantity, and skipped as a non-trade otherwise. A mapped import therefore asks you to confirm that every short sale and cover in the file is named in its warnings (except for an IRA, which can't sell short). An option leg (TO OPEN / TO CLOSE, BTO, STO, STC) whose Symbol names only the underlying, not the contract, isn't imported, and that underlying is withheld. A file is refused when a mapped quantity, price or fee shows a decimal comma (12,50) or a date shows day first (15/03/2026).
- Option exercise, assignment and expiration. An assignment or exercise is modeled (see “Assignment and exercise” below) when your imported trades show everything it needs; otherwise the underlying is withheld. Expiration of a contract you held long is modeled: a lapsed option is treated as sold for $0 on the day it expired (IRC §1234(a)(2)), so the engine adds that $0 sale for the contracts you still held. It's the same closing trade IBKR already exports. The underlying is withheld instead in six cases: the expiration row doesn't name a readable contract and date; it's dated before the contract expires; the option was bought within 30 days before a later loss sale of the same stock (whether an option that expired before that sale still counts as its replacement is unsettled); the rows say the contracts were bought and how many expired, and that's fewer than your imported trades still hold, which can't happen in a complete file (every contract in a series expires together); the account has no trade at all in that contract in the imported history, so the trade that opened it is older than your export; or the row says how many contracts expired (tastytrade's, Fidelity's, Robinhood's and Schwab's rows do) and that's more than your imported trades hold in that account, so the rest were opened before your export. In the last two cases, if you bought the option, its premium is a loss we can't see, and a purchase within 30 days could wash it; the notice names the contract, the date and the account, and says how far back to import. When the row says the contract was written (tastytrade's, Fidelity's, Robinhood's and Schwab's expiration rows can), nothing is withheld (with two exceptions, next): an option you wrote that expires is a gain, never a wash-sale loss, and if the sale that opened it predates your export, the year-end summary names it as missing. One exception is a taxable account where an assignment of that contract took contracts written in the file: if the row names more contracts than your imported trades still hold, first in, first out would have assigned the older contracts, whose premium isn't in the file, and if it names fewer, which contracts were assigned isn't clear, so either way that assignment's underlying is withheld (see “Assignment and exercise”). The other is a row that says the contracts were written when your imported trades hold that contract bought on that day, or bought it by then and hold none written: that's inconsistent, and the underlying is withheld. When the row doesn't say whether it was bought or written, the underlying is withheld unless contracts you wrote in the imported history account for it. On Fidelity's expiration rows we read the Quantity as the number of contracts and its sign as the direction: positive means you wrote them (verified on Fidelity files), and negative is treated as bought (seen in one anonymized sample only); a blank, zero or unreadable Quantity says neither. On Robinhood's expiration rows (OEXP) we read the Quantity: "1S" means the contracts expired from a position you bought and "1" from one you wrote (verified on Robinhood files); a Quantity we can't read says neither. On Schwab's “Expired” rows we read the Quantity as the number of contracts and its sign as the direction: negative means you bought them and positive that you wrote them (checked on 116 rows in 7 public Schwab exports); a blank, zero or unreadable Quantity says neither. An option you wrote whose sale is in the file becomes a year-end row when it expires (below).
- Assignment and exercise. When an option you wrote is assigned, or you exercise one you
bought, the shares change hands at the strike and the option's premium goes into the shares' numbers, as
IRS Pub. 550 says: a put you wrote lowers the basis of the shares it bought (net of the fees you paid to
write it), a call you wrote raises the proceeds of the shares it sold, a call you bought adds its cost to
their basis, and a put you bought takes its cost off the proceeds. The option gets no row of its own, and
your broker's 1099-B makes the same adjustment for options opened since 2014. Shares bought this way are
acquired on the assignment or exercise date (their holding period starts then, unless a wash-sale tack
carries into them), and shares sold this way are sold on that date; that purchase counts for the
wash-sale rule like any other, in any of your accounts (in an IRA, a replacement makes the loss
permanent). The checker models it only when the file names the contract and how many contracts, and your
imported history for that account (that file or any other) has the share row it delivered at the strike
that day and (in a taxable account) the trade that opened the option; with any of those missing or
ambiguous, the underlying is withheld and the dashboard says what's missing. These cases are marked “CPA check” instead of judged, never counted toward an all-clear, and
their numbers are left as they are:
- Fewer contracts were assigned than you had written open, at different premiums: which ones were assigned isn't in the file, so first in, first out picks the premium.
- The broker file doesn't label which share row an assignment or exercise delivered (as in Schwab and E*TRADE files), so the one share row at the strike, quantity and date is used.
- The option you exercised carried a loss disallowed by an earlier wash sale: it goes into the shares, but how that carries through an exercise isn't spelled out.
- A put you bought while you'd held the shares a year or less (or before you bought them): buying a put is generally treated as a short sale, so exercising it restarts the shares' holding period and the sale is short-term.
A put you wrote within 30 days of a loss on the same stock and assigned within those 30 days is a purchase the wash-sale check already counts, so it no longer gets the Rev. Rul. 85-87 “CPA check”; one assigned later still does. A call assigned when you don't hold the shares, or a put exercised when you don't, is a short sale, which still withholds the symbol. Cash-settled options aren't modeled.
- Options you wrote. When an option you wrote (sold to open) on a stock or ETF in a
taxable account is bought back or expires, the year-end summary shows it: the premium you received, less
the opening fees, is the proceeds, and the buy-back price plus the closing fees (zero if it expired) is
the basis. It's reported in the year it was bought back or expired, and it's short-term (IRC §1234(b)),
except that a loss on one written against a position you'd held more than a year, such as a covered call
on shares you've owned that long, can be long-term (IRC §1092(f)); every loss on an option you wrote is
marked “CPA check” (below). Contracts are matched first in, first out, per account, and a roll is two
separate positions.
A position still open has no row yet: the premium isn't income until it closes. Your broker's 1099-B may
show the same result as net proceeds with a $0 basis. Options you wrote don't enter the wash-sale
matching. These cases are marked “CPA check” instead of judged, never counted toward an all-clear, and
their numbers are left as they are:
- A loss on an option you wrote. The wash-sale rule covers options, but how it applies to buying back an option you wrote, and what would count as its replacement (writing the same option again, for example), isn't spelled out in the tax code or IRS guidance.
- A put you wrote, in any of your accounts, within 30 days before or after a loss on the same stock (or on a call you bought). If the put was deep in the money, the IRS treats it as a contract to buy the stock, which can wash the loss (Rev. Rul. 85-87). The checker has no market prices, so it can't tell. (A put assigned within those 30 days is the exception: see “Assignment and exercise”.)
- Straddles. An option you wrote can offset another position: a covered call offsets the shares, and the legs of a spread or a strangle offset each other. A loss on one while the other is open can be deferred (IRC §1092) if, at year end, an offsetting position is still open, or a successor position (one opened within 30 days before or after the loss that offsets a position which offset the one the loss was taken on) or a position offsetting a successor is. An offsetting option you wrote can also change a holding period: a long-term row may be short-term (unless the shares or bought option were already held more than a year when the option was written), and a loss on a covered call may be long-term. A covered call that isn't deep in the money is often exempt, but that depends on prices the checker doesn't have.
If the file shows a buy-to-close with no sale behind it (the option was written before your export starts), an expiration row that says fewer contracts expired than the file holds, or an option you wrote still open after its expiration day with no row saying what happened, that result is left out of the summary and named, and the symbol gets a CPA check. When a row that says the contracts were written says more expired than the file holds, the rest were written before your export: the contracts in the file get their rows and the rest are named, with no CPA check, because an option you wrote that expires is a gain. If the file also has a buy-back of that contract in that account, though, first in, first out can't tell which contracts it closed, so that contract's result in that account is left out and named, and the symbol gets a CPA check. If an assignment of that contract in a taxable account took contracts written in the file, a count that doesn't match (more or fewer) withholds that assignment's underlying instead, because which contracts were assigned, and so the premium in the shares, is unknown (see “Assignment and exercise”). Options you wrote in an IRA have no rows (nothing in an IRA does) and none of these notes, but a put written there still counts for the Rev. Rul. 85-87 check, and any option written there for the straddle checks on your taxable rows. An option you wrote that's assigned has no row of its own: its premium is in the shares' basis or sale price (see “Assignment and exercise”).
- Option trades saved as shares. If an older version of the checker (or a hand-edited backup) stored an option contract as if it were shares, its dollar amounts are off by 100x. That row is excluded, and every verdict for its underlying is withheld until you drop the original broker file in again. The notice names the account and the dates.
- Trade rows we couldn't read. An option trade row whose contract can't be read is listed as an error and not imported, and every verdict for the underlying it names (shares and options on it) is withheld wherever you have trades in it. So is a row labelled as a short sale, cover, or opening/closing leg that the import couldn't classify. The notice describes each row with its date and account, listing the first three and then how many more. Three limits: if the row names no readable ticker, nothing can be withheld automatically, and the import warns you instead; an option row that fails on its date, quantity or price rather than its contract is listed as an error but withholds nothing; and a file imported with an older version of the checker keeps its old rows until you drop it in again, which the dashboard asks you to do for each such file. When a later import adds a trade on the same symbol and date as a row that couldn't be read, the import asks whether it's that row, corrected; the row stays withheld until you say it is. A trade you type in by hand never stands in for it. A backup with an entry that can't be read is refused whole: nothing is restored, and the message names the first problem.
- Data that ends too early. Each account's data counts through the date its files reach, or the date you told us: a range you give at import is capped before the day you imported the file, and a "no trades since" statement counts through the day before you made it. An account whose dates you haven't given counts through the day you uploaded the file holding its most recent trade (an older export imported later doesn't extend it), and the checks that need its dates say "Can't tell". Whichever date that is, the checker then takes the later of it and the account's most recent imported trade. A trade you type in by hand doesn't move that date, because it says nothing about the rest of the account's activity (for an account with nothing imported, the latest typed trade is all there is, so it's used). If a loss's wash-sale window closed after that date for any of your accounts, a purchase there could be invisible. The loss stays in the year-end totals but is marked provisional, and a caveat names the account, the dates to re-import and the dollars at stake. Losses whose window is still open show as danger windows instead. When an account's date rests on a "no trades since" statement, the dashboard says so in a line that names the account and the date.
How the Form 8949 worksheet works
The Form 8949 worksheet is part of Guardian Pro (what it includes). It lines up each sale in your taxable accounts with what your 1099-B likely shows. It is not a Form 8949 and not tax advice.
To see the checker on made-up data without your own files, try it with example trades: 11 made-up trades in three made-up accounts, dated from the day you open it, checked by the same engine, never saved, and with the holdings check skipped. There the Form 8949 worksheet is shown in full, for the made-up trades only.
- Your broker's view. For each taxable account, the checker reruns the same matching engine on that account alone, counting only identical securities (the same ticker, or the same option contract) as replacements. That is the minimum a broker must report: wash sales between identical securities in the same account (Treas. Reg. §1.6045-1(d)(6)(iii)). It compares that with the all-accounts result, sale by sale and lot by lot. The account-alone figures are labelled “likely”: a broker may report more, so your 1099-B can differ. Like the all-accounts figures, they never count shares kept from the purchase a loss sold part of as that loss's replacement (Rev. Rul. 56-602). A broker may count shares kept from the same purchase as replacement shares and report a wash sale for such a sale; if box 1g shows one where the worksheet expects none, check it with a CPA.
- Codes and column (g). What differs becomes a Form 8949 code: B for basis, T for term, W for a wash sale. Column (g) follows the Form 8949 instructions: column (e) keeps the basis your 1099-B shows and the correction goes in column (g), for code W column (g) takes the full disallowed amount, and several codes are listed in alphabetical order.
- “CPA check” instead of a guess. Where the two views can't be lined up lot by lot, where the
rules are unsettled, or where the order of same-day trades matters and the file doesn't show it in one of
the four ways below, the line says “CPA check” and gets no adjustment. It shows both sets of figures. The
same-day cases are:
- Purchases, or sales, of one security in one account on one day at different prices without a trade time for each.
- Two losses sold on the same day, when one purchase replaces one of them and could replace the other (even if other purchases replace all of the other one: whichever loss goes first takes the earliest shares both could use). Treas. Reg. §1.1091-1(b) takes losses in the order sold and, when that can't be determined, in the order the sold shares were bought. No CPA check when the checker's order is that order by the file's trade times or, where those don't order the two sales, by when the sold shares were bought.
- Purchases made on the same day in different accounts that could each replace a loss. The checker takes them in a fixed order (a taxable account's before an IRA's, then by account), while Treas. Reg. §1.1091-1(c)-(d) takes the earliest purchase first. No CPA check when the file's trade times show that the one the checker used came first, or when the loss on one lot takes every share of them that could still replace it, so their order changes nothing. When the checker used an IRA purchase, other IRA purchases that day don't count: either way that part of the loss is permanently disallowed.
- A purchase made in the 30 days before a loss, or on its day, that is sold again on the day of the loss. The checker counts such a purchase as replacing the loss only with what is still held when the loss is sold, and it takes one day's sales in a fixed order (a taxable account's before an IRA's, then by account, then by the file's trade times and other details of each trade). No CPA check when the file's trade times show that the checker's order is the order of the two sales, or when that order can't change what replaces the loss: the checker ran the loss first and didn't use that purchase for it, or ran the loss first and the other order would leave it the same shares of that purchase (the same security, bought in another account before the day of the loss, used by no other sale's loss, the other sale sold only shares of it that hadn't replaced a loss, and enough of it was left for the loss either way), or ran the other sale first and those shares had already replaced other losses, or purchases made before that one replace all of the loss.
- Possibly identical purchases, and chains. A loss with a purchase on the same underlying that the checker flags as possibly substantially identical (tier 3 above) gets a CPA check on the loss line and on that purchase's shares sold on or after the day of the loss. A CPA check about a loss (the four same-day cases above, a possible fill of the same order, the case in this point, or a first-in-first-out order the checker can't be sure of after a wash sale split a purchase) also goes on every line whose basis includes that loss, in either view and in any tax year, and on down any later wash-sale chain. Shares still held that the worksheet lists get these checks too.
- What it doesn't mark. These cases get no CPA check, so check them with a CPA yourself. Different tickers aren't compared at all (see the known limitations below). Shares (or a call) bought within 30 days before a loss and sold again on an earlier day than the loss are never counted as its replacement (the same day is the fourth same-day case above). A CPA check about an option assignment or exercise, an option you wrote, a straddle or a call's holding period stays on its own line: the lines and shares its loss feeds don't get one for it. A possibly noncovered purchase's check also goes to the lines its loss feeds in the same account, because a broker has to report a wash sale only when both the loss sale and the purchase are covered securities.
- Shares you still hold. It also lists shares still held whose all-accounts basis or holding period differs from what the broker likely carries. They aren't on this year's Form 8949, but the difference matters when you sell them.
- Assumptions. Each Form 8949 row assumes covered securities with basis reported to the IRS (box A for short-term, box D for long-term); a purchase that may be a noncovered security (stock bought before 2012, an option bought or written before 2014) gets a CPA check. Sales in an IRA get no lines, but an IRA purchase can still disallow a loss in a taxable account, and the all-accounts figures include that. Each line is rounded to the cent once, so its totals can differ from the year-end summary's by a few cents. Amounts are worked out per share (a trade's fees are spread evenly over its shares), so a line whose exact amount ends in exactly half a cent can be a cent off. The CSV and the printable copy are made in your browser.
"Can I buy it?" and "If you sold at a loss"
Both checks on the dashboard ask the same wash-sale matching described above a question about a trade you haven't made. Each one adds the planned trade to a copy of your imported trades, runs the engine on the copy, and compares the result with the run on your real trades. The copy lives in your browser's memory while the check runs: nothing is saved, your data isn't changed, and nothing leaves your browser. Both are informational: they describe what the files show and never say what to do.
- Can I buy it? You enter a number of shares, a ticker, an account (or an account you haven't added) and a date from today to a year ahead. A date typed as a month and day (10/9) is that day this year; if it has already passed, the check asks for today or a later date instead of reading next year's (type the year, 10/9/2027, for that). The answer repeats the number of shares as the check read it (1.000 is one share). "Would wash a loss" means the planned buy would replace shares from a loss sale in your files that the real run doesn't already count; it gives the amount, the sale, the day the ticker's window ends and the first day a buy no longer washes any open share loss of the ticker (the window is the whole one its card on the dashboard shows, not only the sale this buy would wash). The "next trading day" and "isn't a trading day" lines use a fixed table of NYSE holidays (the exchange's standing rules; unscheduled closings and early closes aren't modeled). They are labels only: every check runs on the calendar date you pick, and a later trade date never adds a conflict with the loss sales in your files. It asks about shares; a call option bought on the ticker can wash a loss on shares too, and the "Why?" line says so.
- When it says "Can't tell." "No conflict found in what you imported" is given only when none of these applies, and each one that does is named in the answer: the ticker is withheld in either run (including a ticker withheld only once the planned buy makes it a traded ticker); a loss on an option on the ticker has a window that reaches the date (whether shares replace an option is a judgment call), and so has an option on the ticker bought in a taxable account that your files show still open after its expiration day with no row closing it (its window is counted from the expiration day); your trades in the ticker have a CPA check: options you wrote on it, an assignment or exercise of it, an option you wrote that your files show still open after its expiration day, or a loss sold within 30 days of the date that is counted as washed by another purchase row on the same day as the one the sold shares came from (the two rows may be fills of one order); your files already have a purchase of the ticker on that date that replaces a loss (the rules don't say which same-day purchase replaces it first); a file was read by an older version of the reader; some imported rows couldn't be read and name no ticker; a taxable account with an unanswered holdings check has a sale of the ticker dated from 30 days before the date through today, so older shares the files don't show could make a gain a loss; the dates your files cover leave a gap in the 30 days before the date (through yesterday: no export can be expected to hold today's trades) in a taxable account, or a taxable account's dates haven't been given, or you said you have an account you haven't added that is taxable or of a type you weren't sure of, or you planned the buy in an account you haven't added (taxable); or a sale of the ticker in a taxable account in those 30 days (or today) rests on trades before them, and the dates your files cover leave a gap there: from 30 days before the sold shares were bought through the sale in the sale's own account, and the 30 days before and after that purchase in your other taxable accounts (and the same for any loss washed into those shares), since a trade in such a gap can change which shares the sale sold or what they cost. A "would wash" answer lists the other reasons that apply under "Also:", because any of them can make its amount wrong. A sale in an IRA never opens a window, so an IRA, or one you haven't added, doesn't need its dates covered. The answer also says how many shares you hold in taxable accounts, since selling them at a loss within 30 days of the buy could be washed by it.
- If you sold at a loss today. For each shares position in a taxable account it lists the purchases in your files that could wash a loss if you sold today. The checker has no prices, so it assumes a price of zero (every share sold at a loss, hence "up to") and never says a sale would be a loss. It sells your oldest shares first (first in, first out). For each position it runs a sale of the whole position and, for up to four smaller amounts (selling everything bought before a recent purchase and keeping that purchase, since the kept shares can replace the sold ones), a sale of that amount. A position whose ticker has no trade other than a sale of shares, and no position event, dated in the last 30 days, and no option trade or option row at any date, needs no run and shows "no purchase … in the last 30 days could replace these shares." A row says "no purchase" only when none of the reasons above applies to it and none of these does; otherwise it says "can't tell" (a row that lists purchases keeps them and lists these reasons under "Also:"): your files have a sale of the ticker (shares or options, in any account) or another entry on it, such as an exercise, assignment or transfer, dated today (the order of same-day entries decides which shares each one sells); a put on the ticker was bought or written that may replace the shares; an option on the ticker, in any account, is shown still open after an expiration day in the last 30 days with no row closing it (an exercise or assignment the files don't show could have delivered shares); a loss counted as washed by a purchase row that may be a fill of the same order was replaced by a fill bought in the last 30 days (read as one order, that fill replaced nothing and could replace shares sold today); or your files show a purchase of the ticker (shares, or a call on it) in the 30 days before today, in any account, that was sold or closed again before today. This checker counts a purchase as replacing a loss only while it is still held when the loss is sold; read literally, the rule counts any purchase in the 30 days before the sale, so such a row can't be checked here. For this list the dates your files cover must include each of the 30 days before today in every account of any type, because a replacement can be an IRA purchase. Options you hold are named but not checked, and a purchase your files don't show (today's, or one in the 30 days after a sale) can also wash. A planned buy in "Can I buy it?" comes after the loss sales in your files; reading the rule literally would count more of your earlier purchases as replacing those losses, which leaves less of them for the buy to wash, so that check doesn't say "Can't tell" for this reason.
- Today, and entries after it. Both checks answer for today's date on this computer. US brokers date a trade in New York, so while this computer's date is already a day later than New York's, a check about today (the list, or a buy dated today) shows a line saying so and gives no answer until it is that date in New York too. Every answer assumes your data holds nothing dated after today: "If you sold at a loss" stops with a line naming the first entry dated after today, anywhere in your data, and "Can I buy it?" does the same when that entry is on the ticker you asked about. Neither gives an answer then.
Known limitations (the honest list)
- FIFO only — no specific-lot election modeling yet.
- No cross-ticker "substantially identical" analysis (e.g., SPY vs VOO) — see our explainer for why that judgment needs a human.
- No short-position wash analysis; no Section 475 mark-to-market accounting.
- Splits and other corporate actions are withheld, not adjusted. We don't yet rebuild lots through them, so those symbols get no numbers until we do.
- What an account's files cover is what you tell the checker. It can't look inside a file to see that every trade is there: a range you give that the file doesn't fully cover (a file filtered to one ticker, say), or a "no trades since" statement that isn't true, lets "Can I buy it?" and "If you sold at a loss" answer "No conflict found in what you imported" or "no purchase" for trades they never saw. An account whose dates you haven't given still counts through the day you uploaded the file holding its most recent trade, and the two checks say "Can't tell" for it. Every answer repeats the dates it used, marking the ones that rest on a statement "(you told us)".
- Gaps at the start of an account's history aren't flagged unless the imported rows show one (sales of more shares than the file bought, for example, or a Robinhood dividend paid on more shares than your imported trades hold) or you report different holdings under the holdings check. If one account's export begins after a loss's window opened, a purchase in that account before the export starts is invisible. Export the longest range each broker allows.
- A purchase counts as replacing a loss only while its shares are still held when the loss is sold; if you bought and sold shares of the same stock in the 30 days before a loss sale, ask a CPA. Read literally, the rule counts any purchase in the 30 days before the sale. So a purchase within 30 days before a loss sale that was sold again before it (on an earlier day, or earlier in the checker's fixed order for one day's sales) is never counted as the loss's replacement: only shares still held when the loss is sold, or bought after it, are. "If you sold at a loss" says "Can't tell" when your files show such a purchase in the 30 days before today.
- When a taxable-account purchase and an IRA purchase on the same day could each be the replacement, we match the taxable one first, so the loss is deferred rather than permanently lost. The law doesn't set an order; the violation note says when this choice mattered, so you can review it with a CPA.
- Broker CSV exports vary and occasionally contain broker-side errors (documented per broker on our broker guides); we surface parse errors and skipped rows rather than hide them.
- Robinhood's export has no fee column: we take a row's fees as the difference between its Amount and Price × Quantity. Robinhood rounds the Price column to the cent, so where that rounding makes Price × Quantity overstate a purchase or understate a sale, that row keeps Price × Quantity (off by at most half a cent a share, plus half a cent). The import warns about those rows, and about any row with no Amount or one that fees and that rounding can't explain, whose fees are counted as $0.00.
- The engine is tested against the worked examples in IRS Pub 550 plus an adversarial test suite, and seven worked examples are public with files you can re-run, but it is informational software, not a tax professional. Reconcile the year-end summary with your CPA.
Primary sources
IRC §1091 · IRC §1256 · Form 6781 and instructions · IRS Publication 550 · Rev. Rul. 2008-5 · Form 8949 instructions · Cboe: index options tax treatment · Nasdaq-100 index options · Cboe: VIX options
Corrections and changes
Fixes to what this site or the checker said or did, newest first. Found something wrong? Tell us.
- 2026-10-05 Three behaviour changes. A row bought on the last day of February can now be read as long-term or short-term differently than before (Rev. Rul. 66-7); a year-end row where the two readings differ carries a CPA check, so a term on that row may have changed. A ledger backup with an entry that can't be read is refused whole, with nothing restored. And a trade a later import adds on the same symbol and date as a row that couldn't be read no longer lifts the withholding by itself: the import asks, and the row stays withheld until you say it is that row, corrected (this goes beyond the 2026-09-30 entry below, which says only that it stays withheld until a corrected file is imported).
- 2026-10-02 The tax-loss-harvesting deadline guide now gives exact dates: for a sale on Dec 31 the window runs through Jan 30, 2027, the first calendar day after it is Jan 31 and the first trading day is Feb 1. Other late-December losses end 30 days after their own sale, not on Jan 30.
- 2026-10-02 The 1099-B guide overstated how often a re-buy at another broker is missing from your 1099-B. It now says it usually isn't there, because brokers may report more than the minimum.
- 2026-10-02 Guides and broker pages no longer promise guaranteed re-buy dates or that every wash sale will be found. They say what the checker shows for the trades you import, and use the rule's own term, "first clean re-buy date".
- 2026-09-30 A trade row the import can't read, or whose amounts aren't in US dollars, is no longer passed over quietly: its stock is withheld and named, and stays withheld until a corrected file is imported.
- 2026-09-30 The 1099-B box 1g guidance now says a broker's amount can be too high or too low, that several wash sales can belong to one sale, and that each window is tied to its own sale date.
- 2026-09-30 A fee cell that can't be read is named in a warning and counted as $0.00, and it never overwrites a fee you imported earlier.
- 2026-09-28 The 61-day window guide's buy-first example contradicted itself. It now says the sale must be more than 30 days after the buy (buy June 1, earliest clean sale July 2).
- 2026-09-27 Danger-window and violation cards no longer say "re-bought" for a purchase made before the sale. Box 1g is described as covering identical (not "substantially identical") re-buys, and a sign error in the TurboTax example's column (g) was fixed.
- 2026-09-27 Robinhood expiration rows are read as bought or written from the quantity, and rows that contradict it withhold the stock. Schwab "Expired" rows are read by the sign of the quantity. A split option root (such as GME1) is withheld instead of treated as a separate stock.
- 2026-09-25 Fidelity's all-accounts download used to be imported into one account, mixing an IRA with a taxable account. It is now split by account. Robinhood fees, which were read as $0, are now taken from each row's Amount.
- 2026-09-25 Shares you keep from a purchase no longer count as the replacement for a loss on the shares sold from that same purchase (Rev. Rul. 56-602).
- 2026-09-25 Option assignments and exercises are modeled, with the premium in the shares' cost or proceeds, instead of withholding every stock that had one. Anything the files don't make clear is still withheld, with a message.
- 2026-09-23 Stock splits, mergers, transfers and unlabeled short sales now withhold the stock with a named message instead of producing invented gains or losses. Options are no longer imported as shares, and BRK/B, BRK.B and "BRK B" now match.
- 2026-09-23 The spouse rule no longer carries a "filing jointly" condition the IRS doesn't state, and a spouse's IRA is described as unsettled rather than settled. "Permanent" no longer describes ordinary wash sales (only a re-buy in your own IRA). The TradeLog, GainsKeeper and Mezzi comparison table was corrected.