Skip to main content

Underlying-agnostic day-by-day backtesting engine for NSE F&O options strategies (pluggable strike selection, delta-drift rebalancing, multi-cycle profit-target/stop-loss exits), backed by a DuckDB cache over the Nubra API. Built to sit behind the options-backtest Claude Code skill.

Project description

NIFTY Options Delta-Neutral Backtesting

A Claude Code skill (.claude/skills/options-backtest/) plus the Python engine behind it (backtest_engine/) that backtests an NSE F&O options strategy — any underlying, any legs, any rebalance/exit rule — described to Claude in plain English. It reports what date ranges are actually testable before running anything, auto-fetches whatever historical data is missing from the Nubra API, and caches everything locally in DuckDB so you never re-fetch the same thing twice.

The skill itself is not NIFTY-specific — the first time it sees a new underlying (BANKNIFTY, a stock, anything on NSE F&O) it discovers that underlying's own data floors from scratch, the same way NIFTY's were originally discovered. What is NIFTY-specific is the dataset that ships in this repo (see below) — a head start, not a limitation of the skill.

What's in this repo

Path What it is
.claude/skills/options-backtest/SKILL.md The skill itself — read this first if you want to understand how Claude uses the engine
backtest_engine/ The Python library (distribution name nubra-backtest-engine): strategy definition API, the day-by-day simulation engine, data fetching/caching, capability checks. Pip-installable and independent of this repo's layout — import backtest_engine works from any project once installed; see Setup below
pyproject.toml Packaging metadata for nubra-backtest-engine
data/market_data.duckdb Pre-fetched NIFTY data — see coverage below
webapp/ Optional browser UI: a real terminal (via node-pty + xterm.js) plus a live read-only dashboard of backfill progress / cached data / results
auth.py One-time interactive Nubra login — run this before anything else

Not included (gitignored, you create your own): .env (your phone number + MPIN) and auth_data.db* (your saved session token). Never commit either — see auth.py's own docstring.

Data that ships with this repo

Cached in data/market_data.duckdb as of this push:

  • NIFTY: 71 expiries (weekly + monthly), 2025-03-27 through 2026-07-28.
    • Option/futures/index OHLC available from 2025-03-24 onward.
    • Greeks (delta/gamma/theta/vega/IV) available from 2026-06-01/02 onward — this floor is independent of the OHLC floor, don't assume they coincide. A rebalance policy that needs delta (e.g. roll-to-ATM on delta drift) will refuse to run on an expiry before this floor rather than silently never rebalancing.
  • BANKNIFTY: one expiry (2026-07-28) cached from an earlier cold-start validation test — everything else about BANKNIFTY (or any other underlying) needs fresh discovery/backfill, same as a completely new project would.

These floors were empirically discovered, not configured — ask the skill and it will re-confirm them live from db.get_expiry_metadata() rather than trusting this table blindly (data may have been backfilled further since this README was last updated).

Setup

  1. Install the backtest_engine package:
    • Not yet on PyPI: pip install git+https://github.com/aaryan-say/nifty-options-delta-neutral-backtest.git
    • (Once published, this becomes pip install nubra-backtest-engine — check PyPI, this note may be stale by the time you read it.)
    • Working from a checkout of this exact repo instead? pip install -e . from the repo root does the same thing, editable.
  2. Install the Claude Code CLI (claude on PATH). Node.js 18+ only if you also want the webapp.
  3. cp .env.example .env, then fill in your real Nubra PHONE_NO and MPIN.
  4. Run py auth.py once, interactively, from this directory — it OTPs you and saves a reusable session to auth_data.db. Every later script/skill run must also be launched from this same directory so it can find that file (and so backtest_engine's DuckDB cache lands in this project's data/, not some other project's).
  5. Launch claude from this directory (or cd webapp && npm install && npm start for the browser UI), and paste the starter prompt below.

Starter prompt

Paste this as your first message in a fresh claude session, launched from this repo's root directory:

I'm using this repo's options-backtest skill. Before we discuss any
strategy, do the following:

1. Check backtest_engine/db.py's cached data (expiry_metadata,
   intraday_metadata) and tell me exactly what's available right now:
   which underlyings, which expiries, the OHLC floor and the Greeks
   floor (they're independent — report both), and any known gaps.
2. Confirm my Nubra authentication is actually working (auth_data.db
   present and valid) before assuming any live fetch will succeed.
3. Tell me plainly: every fetch you make from here on gets cached
   locally in DuckDB automatically, in the same structured tables as
   what's already there, for reuse in future backtests -- that's how
   this system is built, there's no separate "fetch without saving"
   mode. Confirm with me that's fine before you fetch anything new.
4. Ask me whether I want you to proactively pull additional data now
   (tell me for which underlying and date range), or whether you
   should wait until I describe a strategy and then fetch exactly
   what that strategy needs -- either is fine, but ask, don't assume.
5. Then ask me for my strategy in plain English. Map it to a
   StrategyDefinition, check capability BEFORE fetching or running
   anything, fetch only what's missing, run it, and report results
   with timeframe, P&L, number of rebalances, and exit reason. If
   anything about the strategy is ambiguous (exit-policy basis,
   which Support/Resistance method, rebalance threshold, which
   underlying), ask me -- never silently default.

Known limitations to always keep in mind

  • Greeks and OHLC have different floors, for every underlying, not just NIFTY — never assume they coincide.
  • Intraday option bars have been observed to stop publishing the day before expiry (confirmed for NIFTY at 5m) — check capability.check_intraday_capability() before trusting an intraday backtest's last day.
  • Exit-policy basis ("premium" / "underlying_move" / "capital") changes the answer completely — the skill is instructed to always confirm this with you rather than default to one.
  • capital_proxy (net premium collected/paid) is a stand-in for real exchange margin, not the same thing — real SPAN margin needs a registered static IP on the Nubra account (margin.py), and may be blocked entirely depending on your setup.
  • Option prices from the historical API come back in paise, already handled (/100) in data_client.py — if you ever add a new raw API call path, verify against a live quote first.

Publishing nubra-backtest-engine to PyPI (not done yet)

The package builds and installs correctly (pip install -e . and pip install git+<this repo URL> are both tested working — import backtest_engine resolves independent of the repo checkout, and its DuckDB cache path resolves relative to wherever you run it from, not the package's install location) but it is not yet published to real PyPI. No agent can do the actual publish step — it needs a human's own PyPI account and API token. To finish it:

  1. Create a PyPI account at https://pypi.org/account/register/ (email verification + 2FA are mandatory for new accounts).
  2. If the goal is publishing under the same account/org as other nubra-* packages (e.g. nubra-talib, by Akshay N/github akshayn-spec) rather than a fresh standalone account: that requires whoever administers that PyPI project (or a shared Zanskar PyPI Organization, if one exists) to add you as a collaborator/maintainer, or to invite you into the org. That's a request to make internally — it isn't something achievable from here.
  3. Once you have publishing rights (solo or shared), generate a scoped API token from PyPI account settings, then from the repo root:
    pip install build twine
    py -m build
    py -m twine upload dist/*
    
    Twine will prompt for a username/password — use __token__ as the username and the API token (including the pypi- prefix) as the password. Never paste that token into a Claude Code conversation.

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

nubra_backtest_engine-0.1.0.tar.gz (56.0 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

nubra_backtest_engine-0.1.0-py3-none-any.whl (64.2 kB view details)

Uploaded Python 3

File details

Details for the file nubra_backtest_engine-0.1.0.tar.gz.

File metadata

  • Download URL: nubra_backtest_engine-0.1.0.tar.gz
  • Upload date:
  • Size: 56.0 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/7.0.0 CPython/3.12.10

File hashes

Hashes for nubra_backtest_engine-0.1.0.tar.gz
Algorithm Hash digest
SHA256 31786e06548cb11f0780159207e1482e30640bd3c7ad02850c0fe7df9ed85155
MD5 9608e5ad4113d98f5271aa54691e17f8
BLAKE2b-256 7ec3179273a1f241baf12d27eb67cde2c4b46d00709b9d8a8341f21e339270f0

See more details on using hashes here.

File details

Details for the file nubra_backtest_engine-0.1.0-py3-none-any.whl.

File metadata

File hashes

Hashes for nubra_backtest_engine-0.1.0-py3-none-any.whl
Algorithm Hash digest
SHA256 9457b270693ddcf4e3034119d5a70ec962f112a7910d2930218d4ab7daf160f1
MD5 ace7f766532d119893cb243e5cd7c632
BLAKE2b-256 cf577f6c592a039f16661613279f760796d3e3f02ebc39c31d0c274f582f2418

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page