Metadata-Version: 2.4
Name: tradeiq-lab-mcp
Version: 0.2.0
Summary: MCP server for backtesting Indian stock strategies with keyless Yahoo data: write a strategy, backtest it, export a report.
License-Expression: Apache-2.0
License-File: LICENSE
Keywords: algo-trading,backtesting,claude,india,indian-stocks,mcp,model-context-protocol,nse,trading,yahoo-finance
Classifier: Development Status :: 3 - Alpha
Classifier: Intended Audience :: Developers
Classifier: Intended Audience :: Financial and Insurance Industry
Classifier: Operating System :: OS Independent
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3 :: Only
Classifier: Programming Language :: Python :: 3.10
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Programming Language :: Python :: 3.13
Classifier: Topic :: Office/Business :: Financial :: Investment
Classifier: Topic :: Scientific/Engineering :: Information Analysis
Requires-Python: >=3.10
Requires-Dist: markdown-it-py<5,>=3
Requires-Dist: markdown<4,>=3.7
Requires-Dist: matplotlib<4,>=3.9
Requires-Dist: mcp[cli]<3,>=2.0
Requires-Dist: numpy<3,>=1.26
Requires-Dist: pandas<4,>=2.3.0
Requires-Dist: python-docx<2,>=1.1.2
Requires-Dist: weasyprint<72,>=70.0
Requires-Dist: yfinance<2,>=1.4.1
Provides-Extra: dev
Requires-Dist: backtrader<2,>=1.9.78; extra == 'dev'
Requires-Dist: build>=1.2; extra == 'dev'
Requires-Dist: pytest>=8; extra == 'dev'
Description-Content-Type: text/markdown

# tradeiq-lab-mcp

An MCP (Model Context Protocol) server that turns Claude, Codex CLI and other MCP clients into a
strategy-research lab for **Indian stocks**: Claude writes a Python trading strategy, backtests it
on NSE/BSE data with Indian transaction costs, and exports a professional report (PDF, HTML, Word or
Markdown). It also works for other Yahoo Finance symbols as a bonus, but Indian stocks come first.

- **Keyless, no API.** No account, no API key, no hosted backend. Candles come from Yahoo Finance
  through the free `yfinance` library; nothing else leaves your machine.
- **Runs inside your Claude subscription.** There are zero LLM calls in this package. Claude (or
  Codex, Gemini, ...) does all the reasoning on the plan you already pay for; the server only supplies
  data, a backtest engine and report tools.
- **Self-contained engine.** The backtester, 30 indicators, Indian cost model and report writer are
  part of this package. It does not wrap or call any other trading platform.
- **Installs alongside `tradeiq-mcp`.** It is a sibling of the `tradeiq-mcp` research server: a different
  package (`tradeiq-lab-mcp`), server name (`tradeiq-lab`), workspace (`~/.tradeiq-lab/`) and
  deliberately different tool names (`backtest_strategy` and `export_report` here, never `run_backtest`
  or `save_report`), so you can register both and Claude will not confuse them.

> Educational and informational use only. Backtests are indicative, not predictions. **Nothing here is
> investment advice.**

Version 0.2.0 covers **Phases 1 and 2**: data, indicators, backtesting, sandboxed strategies and reports,
plus cross-checks of the engine against independent sources (see [Accuracy](#accuracy)) and parameter
optimization with walk-forward validation, run as background jobs (see [Optimization](#optimization)). See the
[Roadmap](#roadmap) for what is not built yet.

## Install

You need [`uv`](https://docs.astral.sh/uv/) (or plain `pip`) and Python 3.10 or newer. Then add the
server to your client:

**Claude Code**

```bash
claude mcp add tradeiq-lab -s user -- uvx tradeiq-lab-mcp
```

(`-s user` makes it available in every project, not just the current folder.)

**Claude Desktop** - add to `claude_desktop_config.json`
(Windows: `%APPDATA%\Claude\claude_desktop_config.json`):

```json
{
  "mcpServers": {
    "tradeiq-lab": { "command": "uvx", "args": ["tradeiq-lab-mcp"] }
  }
}
```

If Claude Desktop cannot find `uvx` (it is a GUI app and may not see your terminal's PATH), put the
full path to `uvx` in `command` instead - see `where uvx` (Windows) or `which uvx` (macOS).

**Codex CLI**

```bash
codex mcp add tradeiq-lab -- uvx tradeiq-lab-mcp
```

The first launch downloads the dependencies (pandas, matplotlib, WeasyPrint and friends), which can
take a minute or more. If your client times out on it (Codex's default is 10 seconds), pre-warm once
with `uvx tradeiq-lab-mcp --version`, then start the client.

Restart the client, then ask for something like *"Write a simple moving-average strategy, backtest it
on NSE:TCS for 2021-2025 and give me the PDF report."*

Prefer pip? `pip install tradeiq-lab-mcp`, then use `tradeiq-lab-mcp` as the command.

### Or let your AI assistant install it

Copy the prompt for the client you use and paste it into that client's chat. Each prompt is
self-contained and names the exact commands, file paths and checks for that client. They all begin by
pre-warming the download, because some clients give up on a server that takes more than a few seconds
to start.

Clients that only accept remote (hosted) MCP servers, which includes most web chat apps, cannot run
this one, because it runs locally over stdio.

These prompts follow each client's documented MCP configuration format. They have **not** yet been run
end to end on every client and operating system; if a step fails, each prompt tells the assistant to
report the error instead of guessing.

#### Claude Code

```text
Install the TradeIQ Lab MCP server for Claude Code on this computer. TradeIQ Lab is a
Python package on PyPI called `tradeiq-lab-mcp`; it runs locally over stdio and is
launched with `uvx tradeiq-lab-mcp`. Do these steps in order, and stop and tell me
if any step fails.

1. Run `uv --version`. If `uv` is missing, install it with `pip install uv`
   (or `winget install --id=astral-sh.uv -e` on Windows, or
   `curl -LsSf https://astral.sh/uv/install.sh | sh` on macOS/Linux). Then run
   `uvx --version`. If `uvx` is not found on PATH, find its full path (pip puts
   it in the user Scripts/bin folder) and use that full path instead of `uvx`
   in every step below.

2. Pre-warm the download so the first launch is not slow (the package pulls in
   pandas, matplotlib, WeasyPrint and other libraries). Run:
   uvx tradeiq-lab-mcp --version
   and wait for it to print `tradeiq-lab-mcp 0.2.0` (or a later version). This
   can take a few minutes the first time.

3. Run `claude mcp list`. If a `tradeiq-lab` server already exists, remove it with
   `claude mcp remove tradeiq-lab -s user`. Then register it for all my projects:
   claude mcp add tradeiq-lab -s user -- uvx tradeiq-lab-mcp
   (use the full path to uvx if step 1 required it). Leave any separate `tradeiq`
   server (the tradeiq-mcp package) alone: the two work side by side.

4. Run `claude mcp list` and confirm `tradeiq-lab` shows Connected. If it shows a
   failure, run step 2 again and re-check before doing anything else.

5. Do not ask me for API keys and do not add any: this server needs no API key,
   no account and no environment variables.

6. Tell me exactly what you changed. Then tell me to restart Claude Code and
   test with: "List the technical indicators I can use, then show my backtest settings."

If you cannot run commands yourself, do not guess: give me the exact commands
to run by hand.
```


#### Claude Desktop

Claude Desktop's chat cannot edit files or run commands on its own. If you have not enabled a file or
command tool for it, paste this prompt and it will give you the exact steps to do by hand, or paste
it into Claude Code instead.

```text
Install the TradeIQ Lab MCP server for the Claude Desktop app on this computer.
TradeIQ Lab is a Python package on PyPI called `tradeiq-lab-mcp`; it runs locally over
stdio and is launched with `uvx tradeiq-lab-mcp`. Do these steps in order, and stop
and tell me if any step fails.

1. Make sure `uv` is installed: run `uv --version`. If it is missing, install
   it with `pip install uv` (or `winget install --id=astral-sh.uv -e` on
   Windows, or `curl -LsSf https://astral.sh/uv/install.sh | sh` on macOS).

2. Find the FULL path to `uvx` (`where uvx` on Windows, `which uvx` on macOS;
   if it is not on PATH, look in the folder pip or uv installed it to). Use this
   full path, not the bare word `uvx`, because Claude Desktop is a GUI app and
   often does not inherit the PATH your terminal has. On Windows write the path
   with forward slashes (C:/Users/me/.../uvx.exe) so it needs no JSON escaping.

3. Pre-warm the download: run
   <full path to uvx> tradeiq-lab-mcp --version
   and wait for it to print `tradeiq-lab-mcp 0.2.0` (or a later version). The first
   time this takes a few minutes because the package pulls in pandas, matplotlib
   and WeasyPrint.

4. Open the Claude Desktop config file:
   - Windows: %APPDATA%\Claude\claude_desktop_config.json
   - macOS: ~/Library/Application Support/Claude/claude_desktop_config.json
   If it does not exist, create it containing {"mcpServers": {}}. Make a backup
   copy first. Add this entry inside "mcpServers" and keep every server that is
   already there (including a separate "tradeiq" entry, if any):
     "tradeiq-lab": {
       "command": "<full path to uvx>",
       "args": ["tradeiq-lab-mcp"]
     }
   Check the file is still valid JSON afterwards (commas, braces, quotes).

5. Do not ask me for API keys and do not add any: this server needs no API key,
   no account and no environment variables.

6. Tell me exactly what you changed. Then tell me to fully quit Claude Desktop
   (on Windows also from the system tray, not just by closing the window) and
   reopen it. To verify: Settings > Developer should list `tradeiq-lab` as running,
   and asking "List the technical indicators I can use" should call it.
   If it shows an error there, open Claude's log folder, find the
   mcp-server-tradeiq-lab log, and tell me what it says.

If you cannot run commands or edit files yourself, do not guess: give me the
exact steps and the exact file contents to do it by hand.
```


#### Codex CLI

```text
Install the TradeIQ Lab MCP server for Codex CLI on this computer. TradeIQ Lab is a
Python package on PyPI called `tradeiq-lab-mcp`; it runs locally over stdio and is
launched with `uvx tradeiq-lab-mcp`. Do these steps in order, and stop and tell me
if any step fails.

1. Run `uv --version`. If `uv` is missing, install it with `pip install uv`
   (or `winget install --id=astral-sh.uv -e` on Windows, or
   `curl -LsSf https://astral.sh/uv/install.sh | sh` on macOS/Linux). If `uvx`
   is not on PATH afterwards, find its full path and use that instead of `uvx`
   in every step below.

2. Pre-warm the download. This matters more for Codex than for other clients,
   because Codex stops waiting for an MCP server after 10 seconds by default.
   Run:
   uvx tradeiq-lab-mcp --version
   and wait for it to print `tradeiq-lab-mcp 0.2.0` (or a later version); this can
   take a few minutes the first time.

3. Register the server:
   codex mcp add tradeiq-lab -- uvx tradeiq-lab-mcp
   (full path to uvx if step 1 required it). If a `tradeiq-lab` entry already
   exists in the config, update it instead of adding a duplicate.

4. Open ~/.codex/config.toml (Windows: %USERPROFILE%\.codex\config.toml). Back
   it up first. Make the `tradeiq-lab` section read as follows, keeping everything
   else in the file unchanged:
   [mcp_servers.tradeiq-lab]
   command = "uvx"
   args = ["tradeiq-lab-mcp"]
   startup_timeout_sec = 120
   tool_timeout_sec = 600
   The longer timeouts matter: Codex defaults to 10 seconds to start a server
   and 60 seconds per tool call, and a backtest or a PDF export can take longer
   than that. If step 1 needed a full path, put it in `command` (on Windows use
   forward slashes).

5. Run `codex mcp list` and confirm `tradeiq-lab` is listed.

6. Do not ask me for API keys and do not add any: this server needs no API key,
   no account and no environment variables.

7. Tell me exactly what you changed. Then tell me to start a new Codex session
   and check that `/mcp` shows `tradeiq-lab` as active, then test with: "List the
   technical indicators I can use, then show my backtest settings."

If you cannot run commands or edit files yourself, do not guess: give me the
exact commands and file contents to do it by hand.
```


#### Cursor

```text
Install the TradeIQ Lab MCP server for Cursor on this computer. TradeIQ Lab is a Python
package on PyPI called `tradeiq-lab-mcp`; it runs locally over stdio and is launched
with `uvx tradeiq-lab-mcp`. Do these steps in order, and stop and tell me if any
step fails.

1. Run `uv --version`. If `uv` is missing, install it with `pip install uv`
   (or `winget install --id=astral-sh.uv -e` on Windows, or
   `curl -LsSf https://astral.sh/uv/install.sh | sh` on macOS/Linux). Then find
   the FULL path to `uvx` (`where uvx` on Windows, `which uvx` on macOS/Linux).
   Use that full path in the config below: Cursor is a GUI app and may not
   inherit your terminal's PATH. On Windows use forward slashes in the path.

2. Pre-warm the download: run
   <full path to uvx> tradeiq-lab-mcp --version
   and wait for it to print `tradeiq-lab-mcp 0.2.0` (or a later version); this can
   take a few minutes the first time.

3. Open Cursor's global MCP config: ~/.cursor/mcp.json (Windows:
   %USERPROFILE%\.cursor\mcp.json). If it does not exist, create it containing
   {"mcpServers": {}}. Back it up first. Add this entry inside "mcpServers" and
   keep every server that is already there:
     "tradeiq-lab": {
       "command": "<full path to uvx>",
       "args": ["tradeiq-lab-mcp"]
     }
   Check the file is still valid JSON afterwards.

4. Do not ask me for API keys and do not add any: this server needs no API key,
   no account and no environment variables.

5. Tell me exactly what you changed. Then tell me to restart Cursor, open the
   MCP section of Cursor's settings (the Customize option in the sidebar) and
   make sure `tradeiq-lab` is toggled on and lists its tools. If it errors, open the
   Output panel (Cmd/Ctrl+Shift+U), choose "MCP Logs" from the dropdown, and
   tell me what it says. Test with: "List the technical indicators I can use."

If you cannot run commands or edit files yourself, do not guess: give me the
exact steps and file contents to do it by hand.
```


#### VS Code (GitHub Copilot)

```text
Install the TradeIQ Lab MCP server for VS Code (GitHub Copilot Chat, agent mode) on
this computer. TradeIQ Lab is a Python package on PyPI called `tradeiq-lab-mcp`; it runs
locally over stdio and is launched with `uvx tradeiq-lab-mcp`. Do these steps in
order, and stop and tell me if any step fails.

1. Run `uv --version`. If `uv` is missing, install it with `pip install uv`
   (or `winget install --id=astral-sh.uv -e` on Windows, or
   `curl -LsSf https://astral.sh/uv/install.sh | sh` on macOS/Linux). If `uvx`
   is not on PATH afterwards, find its full path (`where uvx` / `which uvx`)
   and use that instead of `uvx` below (forward slashes on Windows).

2. Pre-warm the download: run
   uvx tradeiq-lab-mcp --version
   and wait for it to print `tradeiq-lab-mcp 0.2.0` (or a later version); this can
   take a few minutes the first time.

3. Add the server to my VS Code USER configuration so it is available in every
   workspace. Preferred: on macOS/Linux run
   code --add-mcp '{"name":"tradeiq-lab","type":"stdio","command":"uvx","args":["tradeiq-lab-mcp"]}'
   On Windows PowerShell the quoting is error-prone, so instead open the user
   config file: run "MCP: Open User Configuration" from the Command Palette (or
   edit the `mcp.json` in my VS Code user profile folder). Back it up first.
   Note that VS Code's file uses the top-level key "servers", not
   "mcpServers". Add this and keep every server already there:
     "servers": {
       "tradeiq-lab": {
         "type": "stdio",
         "command": "uvx",
         "args": ["tradeiq-lab-mcp"]
       }
     }
   Check the file is still valid JSON afterwards.

4. Do not ask me for API keys and do not add any: this server needs no API key,
   no account and no environment variables.

5. Tell me exactly what you changed. Then tell me to run "MCP: List Servers"
   from the Command Palette, pick `tradeiq-lab`, choose Start Server (and Show
   Output if it fails), then open Copilot Chat in Agent mode and check that
   `tradeiq-lab` tools appear in the tools picker. Test with: "List the technical
   indicators I can use."

If you cannot run commands or edit files yourself, do not guess: give me the
exact steps and file contents to do it by hand.
```


#### Gemini CLI

```text
Install the TradeIQ Lab MCP server for Gemini CLI on this computer. TradeIQ Lab is a
Python package on PyPI called `tradeiq-lab-mcp`; it runs locally over stdio and is
launched with `uvx tradeiq-lab-mcp`. Do these steps in order, and stop and tell me
if any step fails.

1. Run `uv --version`. If `uv` is missing, install it with `pip install uv`
   (or `winget install --id=astral-sh.uv -e` on Windows, or
   `curl -LsSf https://astral.sh/uv/install.sh | sh` on macOS/Linux). If `uvx`
   is not on PATH afterwards, find its full path and use that instead of `uvx`
   below (forward slashes on Windows).

2. Pre-warm the download: run
   uvx tradeiq-lab-mcp --version
   and wait for it to print `tradeiq-lab-mcp 0.2.0` (or a later version); this can
   take a few minutes the first time.

3. Open my user settings file ~/.gemini/settings.json (Windows:
   %USERPROFILE%\.gemini\settings.json). If it does not exist, create it as
   {}. Back it up first. Add this entry under the top-level "mcpServers" key,
   creating the key if needed, and keep everything already in the file:
     "tradeiq-lab": {
       "command": "uvx",
       "args": ["tradeiq-lab-mcp"]
     }
   Check the file is still valid JSON afterwards.

4. Do not ask me for API keys and do not add any: this server needs no API key,
   no account and no environment variables.

5. Tell me exactly what you changed. Then tell me to start a new `gemini`
   session, run `/mcp` to confirm `tradeiq-lab` is connected and lists its tools,
   and test with: "List the technical indicators I can use."

If you cannot run commands or edit files yourself, do not guess: give me the
exact commands and file contents to do it by hand.
```

## How it works: your assistant does the thinking

There is no LLM inside this server. You describe an idea in plain language; your assistant writes the
strategy code, the server checks and saves it, runs it bar by bar in a separate restricted process
against Yahoo candles with Indian costs applied, and writes the report. A typical session:

1. `search_symbols` / `import_candles` to find and cache the stock's candles (backtests fetch missing
   candles by themselves).
2. `list_indicators` / `get_indicator_details`, then `create_strategy` (the code check runs on save).
3. `backtest_strategy`, then `get_backtest_results` to page through trades and the equity curve.
4. `make_chart` and `export_report` (PDF, HTML, Word or Markdown).
5. Optionally, for a strategy that declares hyperparameters: `optimize_strategy`, then `get_job` until it is
   done, `get_optimize_results` and `export_report` with the `opt-` result id.

Strategies are readable Python files in your workspace, written against a small API (`should_long`,
`go_long`, `update_position`, `self.buy = qty, price`, `self.stop_loss`, `self.liquidate()` ...). A
strategy only ever receives bars up to the current bar, so ordinary strategy code cannot see the future
(deliberately hostile code is covered only by the sandbox's best-effort limits, see SECURITY.md). A
signal on a bar's close fills at the **next bar's open**; when a stop and a target are both hit in one
bar, the stop is assumed to fill first.

Six guides ship with the server as MCP resources (`tradeiq-lab://strategy`, `tradeiq-lab://indicators`,
`tradeiq-lab://backtest_metrics`, `tradeiq-lab://india_costs`, `tradeiq-lab://candles`,
`tradeiq-lab://optimization`), and the server
sends the assistant a set of rules (never fabricate results, show strategy code before running it,
always export a report after a backtest).

## Tools

All 27 tools run locally. None needs an API key.

### Candles (Yahoo Finance data)

| Tool | What it does | Try asking |
|---|---|---|
| `search_symbols` | Look up Yahoo symbols by company name (best effort; can be rate-limited) | "What is the NSE symbol for Tata Consultancy Services?" |
| `import_candles` | Download candles (1d, 1h, 15m, 5m or 1m) for a symbol and cache them locally | "Import daily candles for NSE:TCS from 2021-01-01 to 2025-12-31." |
| `get_candles` | Read cached candles with a summary and data-quality findings (never downloads) | "Show me the last 10 cached daily candles for TCS.NS." |
| `list_candles` | List the candle series cached in your workspace | "What candle data do I already have cached?" |
| `delete_candles` | Delete cached candles; does nothing unless `confirm` is true, and says what would go | "Clear the cached 1-minute candles for NSE:INFY, but tell me what would be deleted first." |
| `export_candles` | Write cached candles to a CSV file in the exports folder | "Export my cached RELIANCE.NS daily candles to a CSV." |

### Indicators

| Tool | What it does | Try asking |
|---|---|---|
| `list_indicators` | The 30 built-in indicators with parameters, defaults and outputs | "Which indicators can I use in a strategy?" |
| `get_indicator_details` | One indicator in detail: meaning, parameters, outputs | "How do I call MACD in a strategy, and what does it return?" |

### Strategies

| Tool | What it does | Try asking |
|---|---|---|
| `create_strategy` | Save a new strategy (Python source) after the code check passes | "Write a strategy that buys when the 10-day average crosses above the 30-day average and exits on the reverse cross, and save it as SmaCross." |
| `read_strategy` | Return the saved source of a strategy | "Show me the code of SmaCross." |
| `update_strategy` | Replace the source of an existing strategy (must pass the code check) | "Change SmaCross to add a 5% stop loss." |
| `list_strategies` | List saved strategies | "Which strategies have I saved?" |
| `delete_strategy` | Delete a saved strategy; does nothing unless `confirm` is true | "Delete the strategy OldTest, but tell me what would go first." |
| `validate_strategy` | Run the static code check on source and write nothing | "Check whether this strategy code is allowed before we save it." |

### Backtests and reports

| Tool | What it does | Try asking |
|---|---|---|
| `backtest_strategy` | Backtest a saved strategy on one stock with Indian costs (delivery or intraday) and save the run | "Backtest SmaCross on NSE:TCS from 2021-01-01 to 2025-12-31 as delivery." |
| `get_backtest_results` | Read a saved run in small pieces: summary, metrics, paged trades or equity curve | "Show me the first 20 trades of that run." |
| `list_backtests` | List saved runs, newest first, with headline numbers | "List my recent backtests." |
| `compare_backtests` | Compare 2 to 5 saved runs side by side | "Compare my last three runs side by side." |
| `make_chart` | Draw an equity, drawdown or trades chart as a PNG | "Draw the drawdown chart for that run." |
| `export_report` | Write the report for a run as PDF, HTML, Word (DOCX) or Markdown | "Export that run as a PDF report." |

### Optimization (background jobs)

| Tool | What it does | Try asking |
|---|---|---|
| `optimize_strategy` | Search a saved strategy's hyperparameters in a background job (walk-forward or train/test split); the headline is out-of-sample and comes with an overfit verdict | "Optimize SmaCross on NSE:TCS from 2019-01-01 to 2025-12-31 with walk-forward validation and 60 trials." |
| `get_job` | Follow a background job: status, progress, estimated time left and the result id | "How far along is that optimization?" |
| `list_jobs` | List background jobs, newest first, with their status and result ids | "What optimization jobs do I have?" |
| `cancel_job` | Cancel a running job; the trials it finished are kept, but there is no result | "Cancel that optimization." |
| `get_optimize_results` | Read a finished optimization: verdict and out-of-sample summary, in-sample trials (paged) or per-fold results | "Show me the verdict and the fold results of that optimization." |

### Settings

| Tool | What it does | Try asking |
|---|---|---|
| `get_settings` | Show backtest settings and the active cost preset | "What are my backtest settings and which cost preset is active?" |
| `update_settings` | Change starting capital, cost preset, risk-free rate, annualization, benchmark, default product, max position size | "Set my starting capital to 5 lakh rupees and the risk-free rate to 6.8%." |

## Optimization

`optimize_strategy` searches the hyperparameters a saved strategy declares (`hyperparameters()`: name, `int` or
`float`, min, max, default; read them in the strategy as `self.hp["name"]`) and then judges the winner on data
the search never saw. It runs as a **background job**, because a search is many full strategy runs and can take
minutes: the call returns a job id at once, and you (or your assistant) follow it with `get_job`, read the
outcome with `get_optimize_results` and get the report with `export_report` (pass the `opt-` result id).

- **Search.** Seeded random search over the declared ranges (`ranges` may only narrow them). Trial 0 is the
  strategy's defaults; a space with no more combinations than `n_trials` is enumerated instead of sampled. The
  objective is `sharpe` (default), `sortino`, `calmar`, `profit_factor` or `total_return`; trials with too few
  trades are rejected, never ranked.
- **Validation.** `walk_forward` (default) cuts the range into `folds + 1` blocks (each at least 60 bars),
  searches on the blocks before fold `j` and runs its best set once on block `j`; the stitched test blocks are
  the **out-of-sample headline** (unless the verdict is `inconclusive`). `split` searches the first `train_pct` and tests once on the rest.
- **Verdict.** `held_up`, `weak`, `overfit`, `inconclusive` or `no_valid_trials`, from how much the
  objective's own value (without the trade factor that ranks trials) degrades out of sample (above 25% is
  `weak`; above 60%, an out-of-sample value at or below zero, or a failed out-of-sample run is `overfit`).
  `inconclusive` has two causes, and the verdict text says which: fewer than `min_trades / 2` out-of-sample
  trades (too few to judge; use a longer range, or a lower `min_trades` knowingly), or, for `total_return`, an
  annualized return that could not be computed for the window (use `sharpe`, `sortino` or `calmar`, or a longer
  range). Either way it says nothing for or against the parameters, and its out-of-sample figures are shown but
  labelled NOT JUDGED, never presented as a validated headline.
  The thresholds are conventions, not statistical tests, and the verdict always comes with the number of
  trials.
- **Jobs.** The job runs in a detached process. Its state lives under `~/.tradeiq-lab/jobs/`, finished results
  under `~/.tradeiq-lab/optimizations/`. At most 2 jobs run at once; `cancel_job` stops one and keeps the
  trials it finished. If the runner dies or the server restarts, the job is shown as `interrupted` with its
  partial trials kept (there is no result to read).
- **What each trial costs.** Every trial is a full strategy run in the sandbox, bar by bar; strategies that
  recompute indicators over the whole history on every bar get slower with the square of the bars. Trials are
  not milliseconds: `get_job` shows an observed time left once a few have finished.

What the result does **not** tell you (read these before relying on a verdict):

- Random search is not exhaustive: a space with continuous floats is only sampled, and the winner is the best
  of what was tried.
- Few folds give noisy verdicts: one out-of-sample window can be lucky or unlucky.
- Walk-forward reduces overfitting but does not remove it. The best in-sample result of N trials is
  optimistically biased, and the more trials, the more so; looking at several strategies, symbols or date
  ranges adds more of the same bias that no verdict accounts for.
- The verdict thresholds (25% and 60%) are conventions. `held_up` means "did not collapse on this unseen
  window", never "will work". The parameters found are not predictions, and no result should be called
  "optimal"; the report labels the recommended parameters as in-sample.
- With `total_return` the verdict compares **annualized** return, which extrapolates hugely on windows shorter
  than a year; prefer `sharpe`, `sortino` or `calmar` there.
- Prices are Yahoo's adjusted prices. The cost preset was dated and compared with one broker's published table
  on 2026-10-15 only (see Accuracy). The engine runs a single stock and a single position at a time, with no
  slippage and no portfolio.
- **Job survival (Windows, measured on the machine this was built on).** The runner survives the MCP server
  exiting, crashing or being terminated on its own, and a host program whose job object permits breakaway. It
  does **not** survive a tree kill of a still-living parent process (for example `taskkill /T`), nor a host
  job object that forbids breakaway: the start then silently falls back to a non-detached process, and
  `get_job` shows `detached: false` and an `interrupted` job says so. Whether Claude's own process job permits
  breakaway is **unverified**. The POSIX branches (a new session and `killpg`) are tested only with fakes, and
  macOS has no process-identity check, so a forced cancel never force-kills there (the runner still stops at
  its next cancel check).

## Indian-market notes

- **Symbols.** Write NSE stocks as `NSE:TCS` or `TCS.NS`, BSE stocks as `BSE:TCS` or `TCS.BO`. The
  Nifty 50 index is `^NSEI` (also the default benchmark). A bare name such as `TCS` is **not** guessed:
  it may be looked up as a US ticker, so the tool descriptions tell Claude to always use the NSE form.
  `search_symbols` helps when you only know the company name.
- **Products.** `delivery` is long-only (a short entry is rejected). `intraday` allows shorts and squares
  positions off at the session end (on daily bars, at that bar's close). Quantities are whole shares and
  order, stop and target prices snap to the 0.05 tick (market fills happen at the bar's adjusted open
  and are not snapped).
- **Costs.** Fills pay brokerage, STT, exchange transaction charges, the SEBI fee, stamp duty and 18% GST
  from a named preset (`india_discount_broker`, or `none` to switch costs off). **The preset is a dated
  snapshot of rates (`rates_as_of` is recorded in `get_settings` and in every report), not a statement of
  current charges: confirm them against your broker.** You can switch presets with `update_settings`;
  editing individual rates through a tool is not available yet. The preset's rates were last compared with
  one broker's published table on 2026-10-15 (see [Accuracy](#accuracy)). Slippage, market impact, DP charges
  and taxes on gains are not modeled.
- **Metrics.** Benchmark `^NSEI`, annualization 252, risk-free rate 6.5% by default (editable). The
  calendar is the data's own trading days. Reports show rupee amounts with Indian digit grouping.
  The annualization setting is per bar whatever the timeframe, so for intraday runs the default 252
  mis-annualizes Sharpe-style metrics. A backtest on a non-daily timeframe adds a run warning (shown in the
  tool result and the report) naming the expected value, for example 1764 for 1h bars, and you fix it with
  `update_settings(annualization=...)`. The expected values assume NSE sessions of about 7 hourly, 25
  15-minute, 75 five-minute or 375 one-minute bars.

## Data sources and limits

Candles are fetched live from Yahoo Finance through the unofficial `yfinance` library and cached in
your workspace. Please read these limits before trusting a result:

- Yahoo prices are **split/dividend-adjusted**, so they differ from the prices printed on contract
  notes.
- **Circuit limits (price bands and trading halts) are not modeled**; a backtest can fill where the real
  market was frozen.
- **Intraday history is short:** 1h about 2 years, 15m and 5m about 60 days, 1m about 7 days. Older
  intraday ranges are rejected with a clear error.
- Yahoo is an **unofficial, rate-limited** source with no service guarantee. Heavy use can get your IP
  throttled; empty or throttled answers surface as errors, never as invented data. Its terms apply to
  your use.
- If Yahoo re-adjusts a stock's history (a split, bonus or dividend), re-importing a range notices the
  change on the overlapping bars and drops the older cached bars outside the imported range, with a finding
  saying so; a daily close-to-close move of more than 30% (a real crash, or a seam in the cache) is flagged
  as a data-quality warning in the run and the report.
- CAGR over a span of under about a year is an extrapolation; reports say so.
- This is research tooling, **not investment advice**. A backtest says how a rule would have behaved on
  past data, not how it will behave.

Every report repeats the first four points in its footer.

## Where data is written

Strategies, cached candles, saved runs, charts and reports live under `~/.tradeiq-lab/` on your
machine (Windows: `C:\Users\<you>\.tradeiq-lab\`). The server has no backend to send them to and
collects no telemetry. Set `TRADEIQ_LAB_HOME` to use another folder. The server's own files are confined
to that workspace; two libraries it uses keep their own caches elsewhere: yfinance writes a cookie and
time-zone cache (`cookies.db`, `tkr-tz.db` under your user cache directory, for example
`%LOCALAPPDATA%\py-yfinance` on Windows) and matplotlib keeps a font cache outside `~/.tradeiq-lab`.

| Folder | Contents |
|---|---|
| `strategies/` | Your strategy files (readable `.py`) |
| `runs/` | Saved backtest runs |
| `jobs/` | One folder per optimization job: state, strategy source, a copy of its candles, parameters and the log of finished trials (not deleted automatically) |
| `optimizations/` | Finished optimization results, one folder per `opt-` id |
| `exports/` | Charts, reports and CSV files (a new report or CSV export never overwrites an existing file; a chart PNG named `<run_id>_<kind>.png` is replaced when you draw it again) |
| `data.db`, `settings.json` | The candle cache and your settings |

Output paths are confined to the workspace, and report text is escaped, so a report cannot run scripts
or fetch remote content.

## Safety of strategy code (read this)

A strategy is Python code, which makes it the one risky input. This server reduces the risk in layers:
a static code check (only numpy, pandas, math, statistics and this package's own API are importable;
`open`, `eval`, `exec`, dunder access and similar are rejected), then execution in a **separate worker
process** with a scrubbed environment (no API keys), a temporary working directory, a wall-clock timeout,
a restricted set of builtins and a runtime guard that refuses file writes, process creation and network
access. Optimization trials run in the same worker (one worker process runs many trials of a job); the
background job runner that starts it is this package's own trusted process and never runs strategy code
itself.

**This is risk reduction, not a perfect sandbox.** Network blocking is best effort, there is no memory
cap on Windows, and name-based code checks can in principle be evaded. Only run strategies that Claude
wrote and you have read. The rules sent to the assistant require it to show you non-trivial code before
running it. Details and limits are in [SECURITY.md](SECURITY.md).

## PDF export needs the Pango library (all platforms)

PDF export uses WeasyPrint, which needs the Pango text-layout library on **every** operating system.
Markdown, HTML and Word (DOCX) exports and everything else in the server do not need Pango, and the
server starts without it; only PDF export fails, with an error that says what to install.

- **Linux:** `libpango-1.0-0` and `libpangoft2-1.0-0` (for example `sudo apt-get install libpango-1.0-0
  libpangoft2-1.0-0` on Debian/Ubuntu).
- **macOS:** `brew install pango`. If Python still cannot find the library, set
  `DYLD_FALLBACK_LIBRARY_PATH=/opt/homebrew/lib` (Apple Silicon Homebrew prefix).
- **Windows:** install either the GTK3 runtime or MSYS2 with `pacman -S mingw-w64-x86_64-pango`, as
  WeasyPrint's documentation describes. If Python cannot find the DLLs afterwards, set the environment
  variable `WEASYPRINT_DLL_DIRECTORIES` to the folder that holds them (the GTK3 runtime's `bin` folder, or
  `C:\msys64\mingw64\bin`), or put that folder on `PATH`.

Honesty note: the Windows machine this was developed and tested on had a GTK3 runtime installed
(`C:\Program Files\GTK3-Runtime Win64\bin` on `PATH`); a plain Python install on Windows would not have
PDF export working until one of the options above is done. The instructions above follow WeasyPrint's
documentation and were not re-tested on a machine without Pango.

## Platform support

Developed and tested on Windows 11 with Python 3.10 and 3.13 (the full offline test suite was run on both; a
real MCP stdio handshake and a tiny offline optimization were run against the built 0.2.0 wheel on both). That machine had the GTK3 runtime installed,
so PDF export worked there; on a Windows machine without Pango, PDF export will not work until you
install it (see above), while every other feature does. The continuous-integration workflow
(`.github/workflows/ci.yml`) is set up to run the offline test suite on Windows, macOS and Linux with
Python 3.10 and 3.13: it installs Pango on Linux and macOS and requires the PDF tests to pass there, while
on the Windows runner the PDF tests stay skippable (no Pango is installed). The maintainers have not yet
seen it run, so treat macOS and Linux as unverified.

## Accuracy

What was checked against what. The evidence, with the exact scope of every check, is in
[docs/validation/2026-10-validation.md](docs/validation/2026-10-validation.md); this section says no more than
that page does.

- **Reference engine and backtrader agree with this engine** (costs off, long-only market orders). An
  independent reference engine written in pandas for the test suite agrees on trade timestamps, quantities,
  prices, fees and final equity, with costs off and on, on three seeded 600-bar synthetic series; the third-party
  engine `backtrader` (a dev-only test dependency, never shipped) agrees on trade count, entry and exit dates,
  per-trade P&L and final equity, costs off. No engine bug was found; three convention differences were
  documented in the guides.
- **Charge rates** were compared with one broker's published table on 2026-10-15. One rate was corrected: the
  exchange transaction charge, 0.00297% to 0.00307%. This says nothing about other brokers, and the DP charge,
  slippage and taxes on gains are not modeled.
- **TradingView check: not yet performed.** Nothing here is claimed as verified against TradingView.
- **Not independently verified:** order types beyond market orders (stop-loss, take-profit, limit and stop
  entries and their gap and same-bar rules) are covered by unit tests only, and so are the intraday product,
  shorts and the square-off at run level. Benchmark and metric formulas beyond what the reference reproduced
  (total return, equity, trade statistics) are not independently verified either. Anything involving real
  Yahoo data rests on Yahoo.

## Known limitations

- CAGR (and the annualized `total_return` verdict) over a window shorter than about a year is an
  extrapolation; reports say so.
- PDF export needs the Pango library on every platform (see the PDF section above); the other formats do not.
- Developed and tested on Windows 11 only (Python 3.10 and 3.13). macOS, Linux and the CI workflow, including
  Python 3.10 there, are unverified.
- Background jobs on Windows do not survive a tree kill of a still-living parent process (`taskkill /T`) nor a
  host job object that forbids breakaway; whether Claude's own process job permits breakaway is unverified; the
  POSIX job-runner branches are tested only with fakes. Details are under Optimization.
- Yahoo is an unofficial, rate-limited source; the other limits (adjusted prices, no circuit limits, short
  intraday history) are listed under Data sources and limits.

## Roadmap

Parameter optimization with walk-forward validation and a background jobs runner is described under
[Optimization](#optimization). The following are planned and are **not yet available**: no tool for any of them exists
in this release.

- **Phase 3:** Monte Carlo robustness tests and a significance test for entry rules.
- **Phase 4:** paper trading.
- **Phase 5:** opt-in research scripts and machine-learning experiments.

## Development

```bash
python -m venv .venv
.venv/bin/python -m pip install -e ".[dev]"   # Windows: .venv\Scripts\python.exe
.venv/bin/python -m pytest -m "not network"   # offline tests
.venv/bin/python scripts/verify_install.py    # real stdio MCP handshake against the installed server
.venv/bin/python scripts/verify_install.py --offline-demo   # ... plus a tiny optimization on synthetic candles
.venv/bin/python scripts/smoke_network.py     # real Yahoo: import + backtest + report + a small optimization (needs network; under a minute on a normal connection, longer if Yahoo is slow or rate-limited)
```

To try a local build before it is on PyPI: `python -m build`, then
`pip install dist/tradeiq_lab_mcp-0.2.0-py3-none-any.whl` in a fresh virtual environment.

## License

Apache-2.0 - see `LICENSE`.
