DEV Community

I tried to port an Indian FII/DII trading strategy to MNQ, MES, SPY, and QQQ - it lost money on all four

What the video claims

Four participant categories move the market: FIIs, DIIs, Proprietary traders, and Retail. Retail traders lose money overwhelmingly - the video cites SEBI data putting this above 90%. The edge comes from daily participant-wise open interest, published by NSE, tracked day-over-day to see who's adding vs. unwinding positions. A second, separate signal comes from option open-interest concentration at specific strikes (e.g. Nifty 24,000) as support/resistance. Treat all of the above as claims from the video, not independently verified facts - in particular I have not verified the "90% of retail traders lose money" SEBI figure myself, I'm relaying what the video states.

Does this exist for US markets?

Short answer: not at the same frequency, and not from any single source.

NSE data Dhamija uses Closest US equivalent What's lost
Daily FII/DII/Pro/Retail open interest CFTC Traders in Financial Futures (TFF) - free, no-auth Socrata API Weekly, not daily. Report is "as of" Tuesday, released the following Friday - a multi-day-old read vs. NSE's next-day data.
Strike-level option OI walls SPY/QQQ/SPX option chain OI (available live via most broker APIs, including Tiger Brokers, which I already had wired up) Broker APIs expose current OI only - no historical time series by strike, so this piece can't be backtested, only paper-traded going forward.

I found this out by actually grepping my environment for existing broker credentials rather than assuming - turns out I already had Tiger Brokers' API configured for live trading, and CFTC publishes their institutional positioning report as a completely free public API. No new accounts or paid data vendors needed.

Because the strike-OI piece isn't backtestable with data I actually have, this post only tests the weekly institutional-bias half of the strategy. That's an important, honest limitation, not a footnote - it's entirely possible the real edge (if any) lives in the untested timing piece.

The signal, in Python

# strategy_logic_fii.py
class WeeklyBiasSignal :
    """ 
    Each week, CFTC reports the leveraged-fund community ' s net change in long/short S&P 500 or Nasdaq-100 futures positioning.
    bias = +1 if leveraged funds net-ADDED to longs this week
           -1 if they net-ADDED to shorts
            0 if flat -> no trade that week
    usable_from = report_date + 6 days: the Monday after the Friday public release
                  -- the first day this information could causally be acted on.
    """
    def __init__ ( self , tff_df ):
        self . tff = tff_df . copy ()
        self . tff [ " report_date " ] = pd . to_datetime ( self . tff [ " report_date " ], utc = True )
        self . tff [ " usable_from " ] = pd . to_datetime ( self . tff [ " usable_from " ], utc = True )
        self . tff = self . tff . sort_values ( " usable_from " ). reset_index ( drop = True )

    def signals ( self ):
        return self . tff [ self . tff [ " bias " ] != 0 ][ [ " report_date " , " usable_from " , " bias " , " lev_money_net_change " , " open_interest_all " ] ]. reset_index ( drop = True )

Trade rule: enter at Monday's open in the direction of bias, hold to that week's Friday close, one position at a time. A 2Γ—ATR(14) stop-loss is applied - sized using only prior days' ATR (shifted, so Monday's stop never peeks at Friday-or-later data) and walked day-by-day through the week with proper gap-fill handling if price opens beyond the stop. The full backtester is event-driven and causal throughout: no signal is ever used before its real public-release date, no fill is assumed instantly at the signal price, and every trade accounts for commission ($0.85/side, an estimate) and slippage (1 tick/side).

The same signal in Pine Script v6

//@version=6
indicator("Weekly Leveraged-Fund Bias (CFTC TFF)", overlay=true)

// Pine can't call an external REST API mid-script, so the weekly CFTC bias
// has to be injected as an external data series -- e.g. via a CSV-backed
// custom symbol imported into TradingView, or manually via input.int() for
// manual/replay use. This is NOT equivalent to the live Python pipeline,
// which pulls CFTC's API directly -- treat this Pine version as a chart
// overlay for eyeballing the signal against price action, not a
// self-contained backtestable strategy.

bias = input.int(0, "Weekly bias (+1/-1/0, set manually per CFTC release)", minval=-1, maxval=1)
atrLen = input.int(14, "ATR length")
atrMult = input.float(2.0, "Stop ATR multiple")

atr = ta.atr(atrLen)

isMonday = dayofweek == dayofweek.monday
var float stopPrice = na

if isMonday and bias != 0
    stopPrice := bias == 1 ? close - atrMult * atr : close + atrMult * atr

plotshape(isMonday and bias == 1, style=shape.triangleup, location=location.belowbar, color=color.green, title="Long bias")
plotshape(isMonday and bias == -1, style=shape.triangledown, location=location.abovebar, color=color.red, title="Short bias")
plot(stopPrice, title="ATR Stop", color=color.orange, style=plot.style_circles)

I want to flag this plainly: the Pine version is a simplification of the Python one, not an independent implementation of the same thing. It can't fetch CFTC's weekly report on its own, so the bias has to be fed in manually or via an imported custom series. If you're going to trust one of these two versions, trust the Python backtest below - that's the one that actually ran against real historical data end to end.

Backtest results

Four instruments, same signal, same 2Γ—ATR(14) stop, real cost assumptions. All figures are trade counts Γ— real historical daily bars - MES/MNQ from ~3.25 years of Tiger continuous-futures data (Apr 2023-Jul 2026), SPY/QQQ from ~6 years of Tiger daily bars (Jul 2020-Jul 2026).

Instrument Trades Win Rate Gross P&L Net P&L (after costs) Profit Factor Sharpe Stopped Out
MES 166 47.0% -$1,725.70 -$2,007.90 0.94 -0.15 18.7%
MNQ 166 52.4% -$6,459.35 -$6,741.55 0.91 -0.26 18.1%
SPY 312 42.6% +$114.12 -$416.28 0.71 -0.99 16.3%
QQQ 312 42.0% +$23.67 -$506.73 0.69 -1.02 17.0%

Every instrument loses money net of costs. Trade counts (166-312) are comfortably above the ~50-trade threshold I use to trust a result at all, so this isn't a small-sample fluke - it's a real, if modest, negative edge for this specific version of the signal.

The stop-loss barely moved the needle versus no stop at all. Only 16-19% of trades ever hit it - the problem here isn't blown-up losing trades, it's that the underlying win rate (42-52%) combined with the payoff structure doesn't clear its own costs. A stop fixes tail risk; it doesn't fix a weak base rate.

Honest caveats

  • This tests roughly half of Dhamija's actual method. The strike-level option-OI timing piece - arguably where his edge, if real, actually lives - isn't backtestable with any data source I have access to (broker APIs only expose current OI, not history). It would need forward paper-testing, not a backtest, to evaluate.
  • CFTC's reporting lag is real and large relative to NSE's. A multi-day-old institutional positioning read is a fundamentally weaker signal than next-day data, independent of whether the underlying idea has merit.
  • Costs are estimates ($0.85/side commission, 1 tick slippage/side), not a confirmed real fee schedule.
  • Fixed weekly hold, no intra-week profit-taking - a real implementation might manage this differently, though I'd want to see it tested rather than assumed to help.
  • MNQ's Tiger continuous-contract data initially had a gap that cut it off ~3 months before the final "as of" date; caught and re-pulled before finalizing these numbers, but worth mentioning as a reminder to always sanity-check a data pull's actual date range rather than trust the fetch script silently.

Bottom line

The honest finding: weekly CFTC leveraged-fund positioning, on its own, is not a profitable signal on MNQ, MES, SPY, or QQQ. This doesn't disprove Dhamija's method on its home turf (NSE) - it means the specific piece of it that's actually testable with public US data doesn't hold up by itself.

If you want to take this further, the two real next steps are (1) get a historical options-OI-by-strike dataset to test the timing layer this post couldn't touch, or (2) forward paper-trade that layer live since it can't be validated historically.

Full code, trade logs, and data-fetch scripts for this post are private for now - ask if you want them.

Comments

No comments yet. Start the discussion.