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

  1. Complete trade-level history: entry/exit time, price, quantity, instrument, computed P&L per trade.
  2. 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).
  3. Bank statements showing fund transfers in/out of the trading account.
  4. 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.

Next: 098 — Psychology: staying hands-off in drawdowns