# AlgoChains — LLM Reference

> This file is intended to be read by AI coding assistants (Cursor, Copilot, Claude, ChatGPT, etc.)
> to understand the AlgoChains library before helping a user write or debug trading strategies.
>
> After `pip install algochains`, full documentation and runnable examples are bundled alongside
> this file. Locate them in Python:
>
>     import algochains
>     print(algochains.__docs_path__)      # directory of markdown docs
>     print(algochains.__examples_path__)  # directory of runnable example strategies
>     print(algochains.__llms_path__)      # path to this file

---

## What AlgoChains is

AlgoChains is a Python quant backtesting library.
- Built on top of Backtrader 1.9.78.123
- Connects to AlgoChains-hosted databases for Forex, Stocks, and Crypto OHLCV history
- Package name: `algochains`

---

## Install

```
pip install algochains
```

---

## API key setup (cross-platform: Windows, macOS, Linux)

IMPORTANT — tell the user to do this before running any code:

Step 1: Create a file named `.env` in their project root directory with the following content,
replacing `your_key_here` with their actual AlgoChains API key from algochains.ai:

```
ALGOCHAINS_API_KEY=your_key_here
```

Step 2: Add `.env` to `.gitignore` so the key is never committed:

```
# .gitignore
.env
```

Step 3: Load it at the top of every script:

```python
from dotenv import load_dotenv
import os

load_dotenv()  # reads .env from the project root — works on Windows, macOS, Linux
```

Then pass it to every `user_query` and `research_query` call:

```python
api_key=os.getenv("ALGOCHAINS_API_KEY")
```

If `os.getenv("ALGOCHAINS_API_KEY")` returns `None`, the `.env` file is missing, in the wrong
directory, or `load_dotenv()` was not called before `os.getenv`.

---

## Public API surface

```python
from algochains import (
    user_query,      # run a backtest
    research_query,  # get raw OHLCV DataFrame
    # analyzers (added automatically by user_query)
    DrawdownTracker,
    PortfolioCandlestickMinute,
    PortfolioCandlestickDaily,
    plot_portfolio_value,
    # commissions (applied automatically)
    ForexCommission,
    StocksCommission,
    CryptoCommission,
)
import backtrader as bt
```

---

## user_query

Run a backtest. Fetches historical data, runs Backtrader cerebro, prints results, saves chart + CSVs.

```python
from dotenv import load_dotenv
import os

load_dotenv()

user_query(
    strategy,                                # bt.Strategy subclass — pass the class, not an instance
    cash,                                    # float — starting portfolio value
    symbol,                                  # str — single asset (use list for multiple, see Multi-asset below)
    start_date,                              # str "YYYY-MM-DD"
    end_date,                                # str "YYYY-MM-DD"
    database,                                # "Forex" | "Stocks" | "Crypto"
    resolution,                              # "1m" | "5m" | "15m" | "30m" | "1h" | "4h" | "1d"
    api_key=os.getenv("ALGOCHAINS_API_KEY"), # str — required for data access
    save_csv=True,                           # write backend_stats.csv, trades.csv, round_trips.csv to cwd
    optimize=False,                          # if True: silent, returns dict instead of None
)
```

optimize=True return value:
```python
{'equity': float, 'net_profit': float, 'return_pct': float, 'max_drawdown': float}
```

---

## research_query

Returns raw OHLCV DataFrame — no Backtrader, no strategy, no chart.

```python
from dotenv import load_dotenv
import os

load_dotenv()

df = research_query(
    symbol,                                  # str or list[str]
    start_date,                              # str "YYYY-MM-DD"
    end_date,                                # str "YYYY-MM-DD"
    database,                                # "Forex" | "Stocks" | "Crypto"
    resolution,                              # "1m" | "5m" | "15m" | "30m" | "1h" | "4h" | "1d"
    api_key=os.getenv("ALGOCHAINS_API_KEY"), # str — required for data access
)
# DataFrame: DatetimeTZDtype index (US/Eastern), columns: open high low close volume (float64)
```

---

## Symbol formats

| database  | format        | examples                              |
|-----------|---------------|---------------------------------------|
| "Forex"   | "BASE-QUOTE"  | "EUR-USD", "GBP-JPY", "USD-BRL"       |
| "Stocks"  | ticker        | "AAPL", "TSLA", "NVDA", "MSFT"        |
| "Crypto"  | "BASE-USD"    | "BTC-USD", "ETH-USD", "LTC-USD"       |

---

## Required strategy params

EVERY strategy must have these two params or AlgoChains will behave incorrectly:

```python
params = (
    ('symbol', 'UNKNOWN'),  # injected automatically by user_query
    ('warmup', 50),         # bars to pre-seed indicators; strategy.next() is gated during warmup
)
```

The warmup gate is injected automatically — you do NOT need to write `if len(self) < warmup: return`.

---

## Strategy skeleton

```python
from dotenv import load_dotenv
import os
import backtrader as bt
from algochains import user_query

load_dotenv()

class MyStrategy(bt.Strategy):
    params = (
        ('symbol',     'UNKNOWN'),
        ('warmup',     50),
        ('rsi_period', 14),
        ('oversold',   30),
        ('overbought', 70),
    )

    def __init__(self):
        self.rsi   = bt.indicators.RSI(self.data.close, period=self.p.rsi_period)
        self.order = None

    def next(self):
        if self.order:      # never stack orders
            return
        price = self.data.close[0]
        if not self.position:
            if self.rsi[0] < self.p.oversold:
                size = int((self.broker.getcash() * 0.95) / price)
                self.order = self.buy(size=size)
        else:
            if self.rsi[0] > self.p.overbought:
                self.order = self.sell(size=self.position.size)

    def stop(self):
        pnl = self.broker.getvalue() - 10_000
        print(f"P&L: ${pnl:+,.2f}")

user_query(MyStrategy, 10000, "BTC-USD", "2025-01-01", "2025-12-31", "Crypto", "1h",
           api_key=os.getenv("ALGOCHAINS_API_KEY"))
```

---

## Accessing price data in next()

```python
self.data.close[0]    # current bar close
self.data.open[0]
self.data.high[0]
self.data.low[0]
self.data.volume[0]
self.data.close[-1]   # previous bar
self.data.close[-2]   # 2 bars ago
```

---

## Injected strategy attributes

Available in next() and stop():

```
self.symbol               str        current symbol
self.backend_stats        dict       per-bar equity/margin data → saved to backend_stats.csv
self.trades               dict       per-fill record → saved to trades.csv
self.round_trips          dict       per-closed-trade record → saved to round_trips.csv
self.entry_price_backend  float|None price of last executed buy
```

---

## Injected methods

```python
self.log("message")   # prints: "YYYY-MM-DD HH:MM:SS | message"
```

---

## Sizing patterns

Stocks (shares):
```python
shares = int((self.broker.getcash() * 0.95) / self.data.close[0])
self.order = self.buy(size=shares)
```

Forex (lots):
```python
self.order = self.buy(size=100_000 * 5)   # 5 standard lots
```

Crypto (fractional, % of portfolio):
```python
size = (self.broker.getcash() * 0.80) / self.data.close[0]
self.order = self.buy(size=size)
```

---

## Multi-asset

Pass a list to `user_query`. The strategy must declare `symbols` (not `symbol`) in its params.

```python
from dotenv import load_dotenv
import os

load_dotenv()

user_query(MyStrategy, 50000, ["BTC-USD", "ETH-USD"],
           "2025-01-01", "2025-12-31", "Crypto", "1d",
           api_key=os.getenv("ALGOCHAINS_API_KEY"))
```

Strategy params for multi-asset — use `symbols` instead of `symbol`:

```python
class MyStrategy(bt.Strategy):
    params = (
        ('symbols', []),   # list — injected automatically for multi-asset
        ('warmup',  50),
    )
```

Accessing feeds in `next()`:

```python
self.datas[0].close[0]   # first symbol (e.g. BTC-USD)
self.datas[1].close[0]   # second symbol (e.g. ETH-USD)
self.datas[0]._name      # "BTC-USD"
```

---

## Commissions

Applied automatically by user_query:
- Forex:  ForexCommission(leverage=30)
- Stocks: StocksCommission(leverage=2)
- Crypto: CryptoCommission(leverage=1)

---

## Output files (backtest)

- backend_stats.csv  — daily: date, portfolio_value, cash_available, used_margin, free_margin, margin_level
- trades.csv         — per-fill: date, symbol, side, size, price, value, commission
- round_trips.csv    — per-closed-trade: entry_date, exit_date, symbol, side, size, entry_price, exit_price, pnl_gross, pnl_net, commission, pnl_pct, holding_minutes

Disable: user_query(..., save_csv=False)

---

## Common errors

| Error message                              | Fix                                                              |
|--------------------------------------------|------------------------------------------------------------------|
| ValueError: Not enough data … for warmup   | Increase params.warmup or extend start_date further back        |
| Indicators return NaN throughout           | warmup too low for indicator lookback period                    |
| os.getenv("ALGOCHAINS_API_KEY") is None    | Check .env exists in project root and load_dotenv() was called  |
| numpy.dtype size changed (ABI error)       | pip install --force-reinstall "numpy<2"                          |
| ModuleNotFoundError: algochains            | Activate venv; pip install algochains                           |

---

## Resolutions

| Value | Bar size |
|-------|----------|
| "1m"  | 1 minute |
| "5m"  | 5 minutes |
| "15m" | 15 minutes |
| "30m" | 30 minutes |
| "1h"  | 1 hour |
| "4h"  | 4 hours |
| "1d"  | 1 day |

---

## Asset classes

| database  | Hours   | Symbol format  | Example symbols            |
|-----------|---------|----------------|---------------------------|
| "Forex"   | 24/5    | "BASE-QUOTE"   | "EUR-USD", "GBP-JPY"      |
| "Stocks"  | 9:30–16 ET | ticker      | "AAPL", "TSLA", "NVDA"   |
| "Crypto"  | 24/7    | "BASE-USD"     | "BTC-USD", "ETH-USD"      |

---

## Parameter optimization with optimize=True

```python
best = None
for period in [7, 14, 21]:
    result = user_query(
        MyStrategy, 10000, "BTC-USD",
        "2025-01-01", "2025-12-31", "Crypto", "1h",
        api_key=os.getenv("ALGOCHAINS_API_KEY"),
        optimize=True,
    )
    if best is None or result['return_pct'] > best['return_pct']:
        best = result | {'period': period}

print(f"Best: {best}")
```

For large grids, use Optuna — see the bundled `examples/walkforward_optuna.py`.

---

## Bundled docs (available after pip install)

Full documentation is bundled in `algochains.__docs_path__`. Files:

| File                  | Contents                                              |
|-----------------------|-------------------------------------------------------|
| installation.md       | Install, virtualenv setup, .env, verify               |
| core-concepts.md      | Warmup, asset classes, commissions, order flow        |
| strategy-api.md       | Strategy anatomy, params, sizing, multi-asset         |
| backtesting.md        | user_query, research_query, optimization              |
| data-feeds.md         | Historical data, timestamps, resampling, multi-asset  |
| troubleshooting.md    | Common errors and fixes                               |

Read a doc in Python:
```python
import algochains, os
with open(os.path.join(algochains.__docs_path__, "strategy-api.md")) as f:
    print(f.read())
```

---

## Bundled examples (available after pip install)

Runnable example strategies are bundled in `algochains.__examples_path__`. Files:

| File                      | What it demonstrates                                      |
|---------------------------|-----------------------------------------------------------|
| simple_sma_cross.py       | SMA crossover on AAPL daily Stocks                        |
| rsi_strategy.py           | RSI mean-reversion on EUR-USD hourly Forex                |
| daily_stocks_algo_qc.py   | Daily stock algo with quality checks                      |
| daily_crypto_algo.py      | Daily crypto algo                                         |
| orb_stocks_algo.py        | Opening range breakout on Stocks                          |
| example_algo.py           | General purpose example                                   |
| walkforward_optuna.py     | Walk-forward optimization with Optuna                     |

Copy an example to your working directory and run it:
```python
import algochains, os, shutil
shutil.copy(os.path.join(algochains.__examples_path__, "simple_sma_cross.py"), ".")
# then: python simple_sma_cross.py
```

---

## Project structure (installed package)

```
algochains/
├── __init__.py
├── llms.txt                    ← this file
├── docs/
│   ├── installation.md
│   ├── core-concepts.md
│   ├── strategy-api.md
│   ├── backtesting.md
│   ├── data-feeds.md
│   └── troubleshooting.md
├── examples/
│   ├── simple_sma_cross.py
│   ├── rsi_strategy.py
│   ├── daily_stocks_algo_qc.py
│   ├── daily_crypto_algo.py
│   ├── orb_stocks_algo.py
│   ├── example_algo.py
│   └── walkforward_optuna.py
├── data/
│   └── backtrader_pull_data.py   user_query, research_query
├── overrides/
│   ├── generic/backtrader_overrides.py   wrap_strategy_*, log, notify_order
│   └── executions/overrides_buy_sell.py  per-asset buy/sell overrides
├── plotting_analyzers/backtrader_analyzers.py
└── commissions/commissions.py
```
