# AutoBrain Sim

A lightweight Python client library for interacting with the [WorldQuant Brain](https://platform.worldquantbrain.com) platform API. Supports authentication, alpha simulation submission, and result retrieval.

## Features

- Multiple authentication methods (direct, credentials file, interactive prompt)
- Submit alpha expressions for simulation
- Poll for simulation completion automatically
- Retrieve alpha details and PnL record sets

## Install

```bash
pip install autobrain-sim
```

## Requirements

- Python 3.7+
- `requests`

Install the dependency:

```bash
pip install requests
```

## Files

| File | Description |
|------|-------------|
| `brain_client.py` | Core client library (`BrainClient`, `SimulationResult`) |
| `example.py` | Standalone usage example |
| `main.ipynb` | Jupyter notebook walkthrough |

## Quick Start

### Authentication

**Method 1 — Direct credentials**
```python
from brain_client import BrainClient

client = BrainClient(email="your@email.com", password="yourpassword")
client.authenticate()
```

**Method 2 — One-liner login**
```python
client = BrainClient.login("your@email.com", "yourpassword")
```

**Method 3 — Interactive prompt** (asks at runtime if no credentials are found)
```python
client = BrainClient()
client.authenticate()
```

**Method 4 — Credentials file**

Create `~/.brain_credentials` (or any path) as a JSON array:
```json
["your@email.com", "yourpassword"]
```

Then load it:
```python
client = BrainClient(credentials_file="~/.brain_credentials")
client.authenticate()
```

> Credentials priority: direct args → `credentials_file` → `~/.brain_credentials` → interactive prompt.

---

### Simulate an Alpha

```python
sim = client.simulate(
    expression="close / ts_mean(close, 20) - 1",
    settings={
        "region": "USA",
        "universe": "TOP3000",
        "neutralization": "SUBINDUSTRY",
    }
)

result = sim.wait(verbose=True)   # blocks until done
print("Alpha ID:", sim.alpha_id)
```

### Retrieve Results

```python
alpha = sim.get_alpha()
print("Sharpe:", alpha["is"]["sharpe"])
print("Fitness:", alpha["is"]["fitness"])

pnl = sim.get_pnl()
print(pnl)
```

### Retrieve Yearly Results

```python
yearly = sim.get_yearly()
for row in yearly:
    print(f"{row['year']}  PnL: {row['pnl']}  Sharpe: {row['sharpe']}  Days: {row['days']}")
```

Or directly from the client using an alpha ID:

```python
yearly = client.get_yearly("YOUR_ALPHA_ID")
```

Each entry contains: `year`, `pnl`, `sharpe`, `days`.

### Run Multiple Alphas in Parallel

```python
expressions = [
    "ts_mean(returns, 5)",
    "ts_sum(volume, 10)",
    "-ts_corr(close, volume, 20)",
]

results = client.simulate_parallel(expressions, workers=3)

for r in results:
    alpha = r.get_alpha()
    print(f"{r.alpha_id}  Sharpe: {alpha['is']['sharpe']}")
```

You can also pass per-expression settings overrides:

```python
results = client.simulate_parallel([
    {"expression": "ts_mean(returns, 5)", "settings": {"universe": "TOP500"}},
    {"expression": "ts_sum(volume, 10)",  "settings": {"universe": "TOP3000"}},
], workers=2)
```

---

## API Reference

### `BrainClient`

| Method | Description |
|--------|-------------|
| `__init__(email, password, credentials_file)` | Initialize client with credentials |
| `BrainClient.login(email, password)` | Create client and authenticate in one step |
| `authenticate()` | Sign in and obtain a session token |
| `simulate(expression, settings, ...)` | Submit an alpha for simulation; returns `SimulationResult` |
| `get_alpha(alpha_id)` | Fetch alpha details by ID |
| `get_pnl(alpha_id)` | Fetch PnL record set for an alpha |
| `get_yearly(alpha_id)` | Fetch PnL grouped by year for an alpha |
| `get_recordset(alpha_id, record_set_name)` | Fetch any named record set |
| `simulate_parallel(expressions, settings, workers, verbose)` | Submit multiple alphas in parallel; returns list of `SimulationResult` |

#### Default Simulation Settings

```python
{
    "instrumentType": "EQUITY",
    "region": "USA",
    "universe": "TOP3000",
    "delay": 1,
    "decay": 15,
    "neutralization": "SUBINDUSTRY",
    "truncation": 0.08,
    "maxTrade": "ON",
    "pasteurization": "ON",
    "testPeriod": "P1Y6M",
    "unitHandling": "VERIFY",
    "nanHandling": "OFF",
    "language": "FASTEXPR",
}
```

Any key passed via `settings` overrides the default.

---

### `SimulationResult`

Returned by `client.simulate()`.

| Method | Description |
|--------|-------------|
| `wait(verbose=True)` | Poll until simulation completes; returns result JSON |
| `get_alpha()` | Fetch full alpha details (call after `wait()`) |
| `get_pnl(poll_interval)` | Fetch PnL record set (call after `wait()`) |
| `get_yearly(poll_interval)` | Fetch PnL grouped by year (call after `wait()`) |

| Attribute | Description |
|-----------|-------------|
| `alpha_id` | Alpha ID string (available after `wait()`) |
| `progress_url` | URL used to poll simulation progress |

---

## Example

```python
from brain_client import BrainClient

client = BrainClient.login()  # interactive prompt

sim = client.simulate("close / ts_mean(close, 20) - 1")
result = sim.wait(verbose=True)

alpha = sim.get_alpha()
print(f"Alpha ID : {sim.alpha_id}")
print(f"Sharpe   : {alpha['is']['sharpe']}")
print(f"Fitness  : {alpha['is']['fitness']}")
```

## Notes

- The client uses HTTP Basic Auth to obtain a session token from the `/authentication` endpoint.
- Polling respects the `Retry-After` response header returned by the Brain API.
- Never commit your credentials to version control. Use `~/.brain_credentials` or environment variables instead.
