Tax rules implemented by taxlots, with hand calculations
All section numbers refer to the Income-tax Act, 1961 as amended up to the Finance Act 2025. Every worked example below is asserted by a test (file and test name are given), so the numbers in this document and the code cannot drift apart silently.
"FY" is the financial year (1 April – 31 March); "AY" is the following assessment year. The rule set is chosen by the sale date (the date of transfer), never by the purchase date, except where a rule says otherwise (sec 50AA, grandfathering).
1. Lot matching — FIFO per ISIN per account
Law. Sec 45(2A), Explanation: for securities held in a depository (demat), "the cost of acquisition and the period of holding of any securities shall be determined on the basis of the first-in-first-out method". CBDT Circular 768 (24-Jun-1998) applies this per demat account. Specific identification is only possible for holdings that are not in demat form (e.g. mutual-fund units held in a statement-of-account folio).
Implementation. One FIFO queue per (account, ISIN). Specific-ID is configured in
taxlots.toml ([[specific_id]]) and is refused unless the account is listed in
[matching] non_demat_accounts.
Hand calc (test_engine.py::test_fifo_across_partial_lots_with_charge_allocation):
| Trade | Qty | Price | Charges |
|---|---|---|---|
| B1 buy 10-Jan-2020 | 100 | 10 | 10 |
| B2 buy 10-Feb-2020 | 50 | 12 | 6 |
| S1 sell 1-Mar-2022 | 120 | 15 | 12 |
- Lot B1 cost = 100 × 10 + 10 = 1,010; B2 cost = 50 × 12 + 6 = 606.
- S1 consumes all of B1 (100) and 20 of B2.
- B2 slice cost = 606 × 20/50 = 242.40; remaining B2 = 30 units at 363.60.
- Sell charges split by quantity: 12 × 100/120 = 10.00 and 12 × 20/120 = 2.00.
- Gains: 1,500 − 10 − 1,010 = 480.00; 300 − 2 − 242.40 = 55.60.
2. Charges
Law. Sec 48(i): "expenditure incurred wholly and exclusively in connection with such transfer" is deductible; costs incurred to acquire the asset form part of the cost of acquisition. STT is not deductible (sec 48, proviso inserted by Finance (No.2) Act 2004).
Implementation. Buy charges are added to the lot's cost and follow it through partial
sales pro-rata by quantity; sell charges are allocated pro-rata by quantity across the lots a
sale consumes. Put brokerage, exchange fees, GST and stamp duty in the charges column — leave
STT out.
3. Holding period — sec 2(42A)
An asset is short-term if held for not more than N months immediately preceding the
date of transfer. taxlots excludes the purchase date and includes the sale date, so for
N = 12 an asset bought on 15-Jan-2023 is long-term on a sale on 16-Jan-2024, not on
15-Jan-2024 (test_rules.py::test_equity_twelve_months_is_strictly_more_than). Month-ends
clamp: bought 29-Feb-2024 → 12 months end 28-Feb-2025.
| Asset type | Sale before 23-Jul-2024 | Sale on/after 23-Jul-2024 |
|---|---|---|
listed_equity, equity_mf, reit_invit |
12 months | 12 months |
gold_etf (listed MF units, non-equity) |
36 months | 12 months (listed security) |
debt_mf bought before 1-Apr-2023 |
36 months | 24 months |
foreign_equity (unlisted in India) |
24 months | 24 months |
debt_mf bought on/after 1-Apr-2023 |
always short-term (sec 50AA) | always short-term |
Finance (No.2) Act 2024 replaced the proviso to sec 2(42A) for transfers on or after 23-Jul-2024: 12 months for all securities listed on a recognised stock exchange in India (which includes listed ETF units), 24 months for everything else.
Deviation from the project spec, deliberately. The spec groups "foreign / gold / debt pre-2023" as 36 → 24 months. That is right for debt funds but not for the other two: foreign shares are shares not listed in India, which have used the 24-month threshold since FY 2016-17, and an exchange-traded gold ETF is a listed security and so uses 12 months after 23-Jul-2024.
Hand calcs (test_rules.py):
* Gold ETF bought 10-Feb-2021: 36 months end 10-Feb-2024 → sale on 10-Feb-2024 is short-term,
on 11-Feb-2024 long-term.
* Debt MF bought 1-Dec-2021, sold 11-Nov-2024 (post-switch, 24 months): long-term from
2-Dec-2023 → long-term.
* Foreign share bought 15-Jan-2021: long-term from 16-Jan-2023.
4. Sec 50AA — specified mutual funds
Units of a "specified mutual fund" acquired on or after 1-Apr-2023 are deemed short-term regardless of holding period and taxed at slab rates (inserted by Finance Act 2023).
- Until FY 2024-25, a specified MF was one investing not more than 35 % in domestic equity — this caught debt funds and gold ETFs / FoFs.
- Finance Act 2025 redefined it (from AY 2026-27, i.e. FY 2025-26 sales) as a fund investing more than 65 % in debt and money-market instruments. Gold ETFs left the definition.
Implementation. debt_mf bought on/after 1-Apr-2023 → 50AA forever. gold_etf bought
on/after 1-Apr-2023 → 50AA only when sold on or before 31-Mar-2025. The pre/post-1-Apr-2023
status is derived from each lot's own acquisition date (after merger carry-over), so
the asset master needs no separate field for it, and a single fund can hold lots on both sides.
Hand calcs (examples, taxlots gains):
* GOLDBEES lot G-02, bought 15-May-2023 @ 48, 500 units sold 10-Feb-2025 @ 62 →
50AA STCG = 31,000 − 24,000 = 7,000 (slab).
* GOLDBEES lot G-04, bought 20-Apr-2024 @ 55, 200 units sold 2-Jun-2025 @ 70 (FY 2025-26):
not specified; long-term from 21-Apr-2025 → sec 112 @ 12.5 % on 14,000 − 11,000 = 3,000
→ tax 375 (test_summary.py::test_gold_etf_fy_2025_26_is_long_term_at_12_5).
5. Rates by sale date
| Section | What | Before 23-Jul-2024 | On/after 23-Jul-2024 |
|---|---|---|---|
| 10(38) | LTCG on STT-paid equity, sold ≤ 31-Mar-2018 | exempt | — |
| 111A | STCG on STT-paid equity, equity MF, business-trust units | 15 % | 20 % |
| 112A | LTCG on the same | 10 % above ₹1,00,000 | 12.5 % above ₹1,25,000 |
| 112 | other LTCG (debt MF pre-2023, gold, foreign) | 20 % with indexation | 12.5 % without indexation |
| — | other STCG, 50AA | slab | slab |
Source: Finance (No.2) Act 2024, amending secs 111A, 112, 112A and 48 for transfers on or
after 23-Jul-2024. Tested in test_rules.py::test_equity_rate_switch_on_23_july_2024
(sale on 22-Jul-2024 → 15 %, on 23-Jul-2024 → 20 %).
Sec 10(38) (sales up to 31-Mar-2018) is supported so that old tradebooks replay correctly
(test_summary.py::test_section_10_38_gain_is_exempt_and_not_set_off). Gains and losses
from exempt sources are kept out of set-off.
The ₹1,25,000 exemption in FY 2024-25
Sec 112A as amended sets the threshold at ₹1,25,000 for the whole of FY 2024-25, applied to the aggregate of LTCG before and after 23-Jul-2024. The Act does not prescribe which portion absorbs the exemption, so taxlots applies it to the 12.5 % gains first (the tax-minimising choice; the ITR utility lets you allocate it).
Hand calc (test_summary.py::test_fy_2024_25_exemption_spans_both_rate_periods_highest_rate_first):
60,000 LTCG at 10 % (June 2024) + 1,00,000 at 12.5 % (Oct 2024). Exemption → 1,00,000 to the
12.5 % bucket, 25,000 to the 10 % bucket. Tax = (60,000 − 25,000) × 10 % = 3,500.
FY 2023-24 uses ₹1,00,000: 1,50,000 LTCG at 10 % → (1,50,000 − 1,00,000) × 10 % = 5,000.
6. Grandfathering — sec 55(2)(ac)
For equity shares, equity-MF units and business-trust units acquired on or before 31-Jan-2018 and taxed under sec 112A, the cost of acquisition is the higher of (a) actual cost and (b) the lower of the FMV on 31-Jan-2018 and the full value of consideration (sale value, before expenses). FMV = highest quoted price on 31-Jan-2018 (NAV for unlisted units). Indexation does not apply under 112A.
Hand calcs (test_engine.py::test_grandfathering_55_2_ac, one unit each):
| Cost | FMV | Sale | min(FMV, Sale) | Cost used | Gain |
|---|---|---|---|---|---|
| 100 | 200 | 250 | 200 | 200 | 50 |
| 100 | 200 | 150 | 150 | 150 | 0 |
| 300 | 200 | 250 | 200 | 300 | −50 |
| 100 | 200 | 80 | 80 | 100 | −20 |
Rows 2 and 4 show the two intended effects: grandfathering can wipe out a gain but can never create an artificial loss, and it never reduces actual cost.
How FMV follows corporate actions. The asset master stores the FMV as quoted on
31-Jan-2018. taxlots stamps it onto every lot open at the end of 31-Jan-2018 and then
scales it with any later split/merger. A split before 2018 needs no adjustment, because the
quoted FMV is already post-split (test_split_before_2018_uses_post_split_fmv_as_quoted).
Example: INFY sale 1012 (10-Jun-2024, 150 @ 1,500; FY 2024-25, 10 % regime): * Lot 1002: 100 bought 15-Jun-2016 @ 950 → cost 95,000; FMV 1,180 × 100 = 1,18,000; sale 1,50,000. Cost used = max(95,000, min(1,18,000, 1,50,000)) = 1,18,000; gain 32,000. * Bonus lot (1:1 on 4-Sep-2018), 50 units: cost 0, acquired after 31-Jan-2018 → no grandfathering; gain 50 × 1,500 = 75,000. Total 1,07,000 at 10 %.
7. Corporate actions
| Action | Law | Effect on lots |
|---|---|---|
| Split / consolidation | not a transfer; cost apportioned | qty × new/old, total cost unchanged, FMV ÷ ratio, acquisition date unchanged |
| Bonus | sec 55(2)(aa)(iiia): cost nil; holding period from allotment date | new lot of ⌊held × bonus/held⌋ units, cost 0, acquired on the allotment date; original lots untouched |
| Amalgamation / rename | sec 47(vii) not a transfer; sec 49(2) cost carried; sec 2(42A) Expl. 1(b) holding period includes the old shares | lots move to the new ISIN with qty × new/old, cost and acquisition date carried |
Split example: HDFCBANK sale 1023 (test_split_after_2018_rescales_fmv).
50 bought 10-Mar-2017 @ 1,200 (cost 60,000), FMV 31-Jan-2018 = 1,990. Split 1:2 on
19-Sep-2019 → 100 shares, FMV per share 995. Sold 100 @ 1,650 on 20-Jan-2025:
cost used = max(60,000, min(99,500, 1,65,000)) = 99,500; gain = 1,65,000 − 99,500 =
65,500 at 12.5 %.
Bonus trap (test_bonus_shares_sold_soon_after_allotment_are_short_term): 10 shares held
since 2015, bonus 1:1 on 28-Oct-2024, all 20 sold on 15-Jan-2025 @ 150 (cost 100). The 10
originals are LTCG (500); the 10 bonus shares were acquired less than 12 months ago → STCG
111A at 20 % on the full 1,500.
Merger example: HDFC → HDFCBANK, sale I-02 (test_merger_carries_cost_and_acquisition_date).
25 HDFC bought 1-Aug-2016 @ 2,000 + 50 charges = 50,050; FMV 1,860. Merger 42:25 on
13-Jul-2023 → 42 HDFCBANK, FMV per share 1,860 × 25/42, total FMV 46,500. Sold 42 @ 1,700 on
5-Mar-2025 with 71.40 charges: cost used = max(50,050, min(46,500, 71,400)) = 50,050; gain =
71,400 − 71.40 − 50,050 = 21,278.60.
8. Indexation — sec 48, second proviso; CII
Indexed cost = cost × CII(year of transfer) / CII(year of acquisition). Available only for
sec 112 LTCG on transfers before 23-Jul-2024. The CII table (base FY 2001-02 = 100,
CBDT Notification 44/2017) is embedded in rules.py:
| FY | CII | FY | CII | FY | CII |
|---|---|---|---|---|---|
| 2001-02 | 100 | 2010-11 | 167 | 2019-20 | 289 |
| 2002-03 | 105 | 2011-12 | 184 | 2020-21 | 301 |
| 2003-04 | 109 | 2012-13 | 200 | 2021-22 | 317 |
| 2004-05 | 113 | 2013-14 | 220 | 2022-23 | 331 |
| 2005-06 | 117 | 2014-15 | 240 | 2023-24 | 348 |
| 2006-07 | 122 | 2015-16 | 254 | 2024-25 | 363 |
| 2007-08 | 129 | 2016-17 | 264 | 2025-26 | 376 |
| 2008-09 | 137 | 2017-18 | 272 | ||
| 2009-10 | 148 | 2018-19 | 280 |
Source: CBDT notifications under sec 48 Explanation (v); FY 2024-25 = 363 (notified May 2024), FY 2025-26 = 376 (notified July 2025). Assets acquired before 1-Apr-2001 (where the FMV-on-1-Apr-2001 substitution applies) are out of scope and raise an error if indexation is needed.
Hand calc: CORPBOND-A sale C-05 (test_indexation_for_pre_switch_debt_fund).
1,000 units bought 15-Apr-2019 (FY 2019-20, CII 289) @ 21.50; sold 20-May-2024 (FY 2024-25,
CII 363, before 23-Jul-2024) @ 28.40. Held > 36 months → LTCG sec 112 @ 20 %.
Indexed cost = 21,500 × 363 / 289 = 27,005.19; gain = 28,400 − 27,005.19 = 1,394.81.
Foreign equity: AAPL sale F-02 (FY 2023-24). 10 bought 15-Jan-2021 (FY 2020-21, CII 301) at ₹9,500 + ₹250 charges = 95,250; sold 10-Aug-2023 (CII 348) at ₹14,800 less ₹300 charges. Indexed cost = 95,250 × 348/301 = 1,10,122.92; gain = 1,47,700 − 1,10,122.92 = 37,577.08.
9. Intraday = speculative business income — sec 43(5)
A purchase and sale of the same share on the same day without delivery is a speculative transaction (sec 43(5)); its result is business income, not capital gains. Speculative loss can be set off only against speculative income and carried forward for 4 AYs (sec 73(1), 73(4)).
Implementation. For each (account, ISIN, date) of listed_equity, intraday qty =
min(bought, sold) that day. That quantity is taken from the day's buys and sells in order
(charges pro-rata); the remainder flows into FIFO as delivery. Mutual funds and other assets
are never treated as intraday.
Hand calc (test_intraday_is_separated_from_delivery): 20 shares held from 2020 @ 50.
On 5-Nov-2024: buy 30 @ 100 (charges 3), sell 50 @ 110 (charges 5).
* Intraday 30: 3,300 − 3,000 − (3 + 5 × 30/50) = 294 speculative income.
* Delivery sale 20 @ 110, charges 2, FIFO from the 2020 lot: 2,200 − 2 − 1,000 = 1,198 LTCG.
10. Set-off and carry forward — secs 70, 71, 74
- Sec 70(2): short-term capital loss can be set off against any capital gain.
- Sec 70(3): long-term capital loss only against long-term capital gain.
- Sec 71(3): capital losses never reduce other heads.
- Sec 74: unabsorbed losses carry forward for 8 AYs, under the same restriction, if the return is filed by the sec 139(1) due date (sec 80).
Losses must be set off in the year they arise; you cannot choose to carry a loss forward while gains exist. The 112A exemption applies to the gain that remains after set-off. taxlots therefore:
- nets gains and losses inside each bucket (as the ITR's Schedule CG does);
- applies current-year LTCL, then STCL, then brought-forward LTCL, then STCL;
- each loss goes to the highest-rate eligible bucket first, and to the part of a 112A bucket covered by the exemption only once every taxable gain is used up.
LTCL is applied before STCL because it has fewer eligible targets; with eligible sets nested like this, greedy highest-rate-first is optimal.
Hand calc 1 (test_stcl_goes_to_highest_rate_gains_first, FY 2024-25, slab 30 %):
STCL 10,000 (111A); 50AA gain 4,000; 112A gain 2,00,000.
STCL → 4,000 against 50AA (30 %), 6,000 against 112A (12.5 %).
112A taxable = 2,00,000 − 6,000 − 1,25,000 = 69,000 → tax 8,625; cess 4 % = 345;
total 8,970.
Hand calc 2 (test_ltcl_cannot_reduce_stcg_and_is_carried_forward_8_years):
STCG 50,000 at 20 % and LTCL 20,000. LTCL cannot touch STCG → tax 10,000 + 400 cess;
LTCL 20,000 carried forward, usable until AY 2033-34 (FY 2024-25 = AY 2025-26, plus 8).
Hand calc 3 (test_ltcl_set_off_is_mandatory_even_against_exempt_112a_gain):
112A gain 1,00,000 (fully inside the exemption) and a debt-fund LTCL of 30,000. The loss
must still be set off: 112A becomes 70,000, all exempt; tax 0; nothing is carried forward.
(This is the well-known "wasted loss" — the tool reports it rather than hiding it.)
11. Tax estimate, surcharge and cess
- Special-rate buckets use their own rate; slab buckets (50AA, other STCG) and speculative
income use
--slab-rate(default 30 %, configurable intaxlots.toml). --surchargeis applied in full to slab-taxed amounts but capped at 15 % on tax under 111A, 112A and 112 (Finance Act 2022 extended the cap to all LTCG).- Health & Education Cess: 4 % on tax + surcharge.
Hand calc (test_surcharge_capped_at_15_percent_on_special_rate_gains, surcharge 25 %):
111A 1,00,000 × 20 % = 20,000 → surcharge capped 15 % = 3,000. 50AA 10,000 × 30 % = 3,000 →
surcharge 25 % = 750. Cess = (23,000 + 3,750) × 4 % = 1,070.
12. Full worked example — FY 2024-25 from examples/
taxlots summary --fy 2024-25 (asserted by test_cli.py::test_summary_fy_2024_25_matches_hand_calculation):
| Bucket | Items | Net |
|---|---|---|
| 111A @15 % | TATAMOTORS 1013: 200 × (980 − 900) | 16,000.00 |
| 111A @20 % | RELIANCE 1017 +5,000; TATAMOTORS 1021: 100 × (800 − 1,100) = −30,000 | −25,000.00 |
| 50AA | GOLDBEES G-05 | 7,000.00 |
| 112A @10 % | INFY 1012 (32,000 + 75,000) | 1,07,000.00 |
| 112A @12.5 % | IRCTC 35,400; PPFAS 16,350; EMBASSY −1,000; SBIN 48,000; HDFCBANK 65,500; HDFCBANK (icici) 21,278.60 | 1,85,528.60 |
| 112 @20 % indexed | CORPBOND-A C-05 | 1,394.81 |
| 112 @12.5 % | CORPBOND-A C-07: 500 × (29 − 25) | 2,000.00 |
- IRCTC 1016: 20 bought @ 1,800 = 36,000, split 1:5 → 100 @ 360; 60 sold @ 950 → 57,000 − 21,600 = 35,400.
- PPFAS C-06: 300 units bought 10-Nov-2017 @ 22 (6,600), FMV 23.50 (7,050) → 23,400 − 7,050 = 16,350.
- SBIN 1022: FIFO takes the 2016 lot (bought @ 160, FMV 310), not the August 2024 lot: 79,000 − max(16,000, min(31,000, 79,000)) = 48,000.
Set-off of the 25,000 STCL, highest rate first: 7,000 → 50AA (30 %), 1,394.81 → 112 @20 %, 16,000 → 111A @15 %, the last 605.19 → a 12.5 % bucket.
Exemption 1,25,000 → 112A @12.5 %. Taxable: 112A @10 % 1,07,000; 112A @12.5 % 1,85,528.60 − 605.19 − 1,25,000 = 59,923.41; 112 @12.5 % 2,000. Intraday TCS: +300 at 30 %.
Tax = 10,700 + 7,490.43 + 250 + 90 = 18,530.43; cess 741.22; total 19,271.64.
13. Tax-gain harvesting
Goal: realise long-term 112A gains up to the unused exemption (default ₹1,25,000), re-buy, and so step up the cost basis for free. Two constraints from the law shape the algorithm:
- Demat sales are FIFO, so a holding can only be sold from the oldest lot forward.
- Only 112A gains benefit; a lot that is still short-term would produce 111A tax.
So for each (account, ISIN) taxlots takes the longest prefix of lots that are long-term
on the planned date and in profit, works out the gain per unit (with grandfathering), and
greedily fills the budget, holdings with the highest gain per unit first (fewest units to
trade), selling whole shares (0.001 units for mutual funds).
Hand calc (test_harvest.py::test_fills_budget_highest_gain_per_unit_first): budget
3,000 − 500 already realised = 2,500. EQ1: 5 @ 50, price 1,000 → 950/unit. EQ2 lots: 10 @ 100,
10 @ 150, 10 @ 190 (short-term), price 200 → 100/unit, then 50/unit.
EQ1 first: ⌊2,500/950⌋ = 2 → 1,900, 600 left. EQ2 lot 1: ⌊600/100⌋ = 6 → 600. Remaining 0.
Example run (FY 2025-26, as of 31-Mar-2026, 23,000 already realised from ITC): INFY bonus 50 × 1,550 = 77,500; IRCTC 40 × (780 − 360) = 16,800; ITC 36 × (410 − 200) = 7,560; PPFAS 2.170 × (88 − 23.50) = 139.97. Total 1,01,999.97 of the 1,02,000 budget.
Limitations (deliberately out of scope)
- Land and buildings, unlisted Indian shares, bonds, sec 54/54EC/54F exemptions, sec 50C.
- Adjusting the basic exemption limit against 111A/112A gains for low-income residents; sec 87A rebate; non-resident rules (no indexation for NRIs, TDS).
- Dividend/bonus stripping (sec 94(7)/(8)) is not detected.
- Foreign equity prices must already be in INR (Rule 115 uses the SBI TT buying rate).
- No asset type for unlisted gold / international fund-of-funds (24 months after
23-Jul-2024, 50AA rules depend on the year);
debt_mfis only a rough stand-in. - FMV values in
examples/assets.tomlare illustrative — use the real 31-Jan-2018 high. - Cash in lieu of fractional bonus/merger entitlements is ignored.
- Sales before FY 2017-18 use today's holding-period thresholds for non-equity assets.