Record-keeping and turnover for tax audit
General education, not tax advice — verify current thresholds and rules with a CA. Continuing directly from chapter 96.
Turnover for F&O is NOT your traded value — this is the most common misunderstanding
Many new algo traders assume "turnover" means total value of contracts traded (e.g. buying and selling ₹50 lakh worth of NIFTY futures over the year = ₹50 lakh turnover). It doesn't. For F&O, turnover under prevailing guidance (ICAI guidelines referenced in tax audit rules) is computed roughly as:
Turnover = sum of absolute profit/loss on every settled trade
+ premium received on options sold
+ (in some interpretations) the difference on reverse trades
def approximate_fno_turnover(trades: pd.DataFrame) -> float:
"""
trades: columns [symbol, side, qty, price, instrument_type]
This is a simplified approximation — exact turnover computation
for tax purposes should follow current ICAI guidance and be
verified with a CA, especially for options premium treatment.
"""
# Group into round-trip trades, sum absolute P&L
round_trips = pair_entries_and_exits(trades) # your own pairing logic
pnl_turnover = round_trips["pnl"].abs().sum()
# Add options premium received on short (sell-to-open) positions
options_sold = trades[(trades.instrument_type.isin(["CE", "PE"])) & (trades.side == "SELL")]
premium_turnover = (options_sold["qty"] * options_sold["price"]).sum()
return pnl_turnover + premium_turnover
An algo strategy trading frequently, even with modest capital, can generate absolute-P&L turnover that crosses the tax audit threshold far faster than the capital deployed would suggest — because turnover compounds across every single trade's P&L magnitude, win or loss, not net capital movement.
Why this changes how you should structure your journal from day one
class TaxAwareJournal(TradeJournal): # extends chapter 89's journal
def record_exit(self, trade_id, exit_price, pnl, instrument_type, premium_if_short_option=None, notes=""):
classification = self._classify(instrument_type)
super().record_exit(trade_id, exit_price, pnl, notes)
self.conn.execute(
"UPDATE trades SET classification=?, premium=? WHERE trade_id=?",
(classification, premium_if_short_option, trade_id),
)
self.conn.commit()
def _classify(self, instrument_type: str) -> str:
if instrument_type in ("FUT", "CE", "PE"):
return "non_speculative_business"
return "speculative_business" # intraday equity — adjust logic for CNC delivery separately
Records you should be able to produce on demand for a CA or an audit
- Complete trade-level history: entry/exit time, price, quantity, instrument, computed P&L per trade.
- Broker-issued contract notes / tax P&L statements (most brokers, including Zerodha via Console, provide an annual tax P&L report — use this as your cross-check against your own journal, not as a replacement for keeping your own records).
- Bank statements showing fund transfers in/out of the trading account.
- Brokerage/charges statements (for expense claims against business income).
Practical takeaway
Reconcile your own journal against the broker's official tax P&L statement (usually available for download, e.g. via Zerodha Console) periodically through the year, not just once at filing time — catching a discrepancy in October is far less stressful than catching it in July while your CA is waiting on numbers for a filing deadline.