Metadata-Version: 2.4
Name: nubra-backtest-engine
Version: 0.1.0
Summary: 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.
Author-email: aryanbrs <aryannnyadavvv@gmail.com>
License-Expression: MIT
Project-URL: Repository, https://github.com/aaryan-say/nifty-options-delta-neutral-backtest
Requires-Python: >=3.10
Description-Content-Type: text/markdown
Requires-Dist: nubra_sdk
Requires-Dist: pandas
Requires-Dist: duckdb

# 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](https://claude.com/claude-code) (`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.
