> ## Documentation Index
> Fetch the complete documentation index at: https://docs.dexpaprika.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Feed an AI trading agent with technical indicators

> Give an AI agent RSI, MACD and Bollinger Bands for a watchlist of DEX pools as a compact JSON state, computed by the DexPaprika API, and let it decide buy, sell or hold. Runs without a key; a free key opens a week of history.

## What you'll build

A paper-trading agent that wakes once an hour, reads RSI, MACD and Bollinger Bands for three DEX pools straight from the API, and asks a language model to decide buy, sell or hold for each, next to a plain rule-based baseline.

The idea is the one behind live model-trading benchmarks such as [Alpha Arena by Nof1](https://www.nof1.ai): hand the model a compact set of ready signals per market and let it spend its reasoning on the decision rather than on arithmetic. The [indicator endpoints](/tutorials/technical-indicators) compute those signals on the server from the same candles as [pool OHLCV](/tutorials/retrieve-historical-data), so the agent's numbers match the chart you would check by hand.

This is for paper trading and research. Indicators describe the past; nothing here is financial advice.

***

## What the agent sees

For each pool, one line of JSON per hour:

```json theme={null}
{"pool": "SOL/USDC, Orca", "network": "solana", "candle": "2026-10-13T11:00:00Z", "close": 106.168135, "rsi_14": 39.5, "macd_histogram": 0.055981, "macd_cross": null, "bbands_percent_b": 0.31, "bbands_width_pct": 4.99}
```

| Field | From | How to read it |
| - | - | - |
| `rsi_14` | `indicator=rsi` | Over 70 is commonly read as overbought, under 30 as oversold |
| `macd_histogram`, `macd_cross` | `indicator=macd` | `macd_cross` is `up` or `down` when the histogram changed sign on the last candle, which means MACD crossed its signal line |
| `bbands_percent_b` | `indicator=bbands` and the candle's close | 0 at the lower band, 1 at the upper; below 0 or above 1 is outside the bands |
| `bbands_width_pct` | `indicator=bbands` | Band width as a share of the middle band. Narrow bands often come before a large move |
| `close` | [pool OHLCV](/api-reference/pools/get-ohlcv-data-for-a-pool-pair) | The close of the same candle the indicators end on |

About 220 characters per pool. The 24 hourly candles behind it are about 4,600 characters of JSON for the same pool, a week of them seven times that, and the model would still have to do the maths.

***

## Run it

```bash theme={null}
pip install requests anthropic
export DEXPAPRIKA_API_KEY=your_free_key      # optional, see below
export ANTHROPIC_API_KEY=your_anthropic_key  # optional, without it only the rules run
python agent.py
```

```python theme={null}
import json
import os
import time

import requests

API = "https://api.dexpaprika.com"
KEY = os.environ.get("DEXPAPRIKA_API_KEY")  # a free key from console.dexpaprika.com
HEADERS = {"Authorization": KEY} if KEY else {}
START = "-7d" if KEY else "-24h"  # the history window of each plan
INTERVAL = "1h"

WATCHLIST = [
    ("ethereum", "0x88e6a0c2ddd26feeb64f039a2c41296fcb3f5640", "WETH/USDC, Uniswap v3"),
    ("solana", "Czfq3xZZDmsdGdUyrNLtRhGc47cXcZtLG4crryfu44zE", "SOL/USDC, Orca"),
    ("base", "0x6c561b446416e1a00e8e93e221854d6ea4171372", "WETH/USDC, Uniswap v3"),
]


def get(path, **params):
    for _ in range(5):
        r = requests.get(f"{API}{path}", params=params, headers=HEADERS, timeout=30)
        if r.status_code == 429:
            time.sleep(int(r.headers.get("Retry-After", "5")) + 1)
            continue
        r.raise_for_status()
        return r.json()
    r.raise_for_status()


def indicator(network, pool, name):
    path = f"/networks/{network}/pools/{pool}/indicators"
    return get(path, indicator=name, interval=INTERVAL, start=START)["data"]


def snapshot(network, pool, label):
    rsi = indicator(network, pool, "rsi")[-1]
    prev, macd = indicator(network, pool, "macd")[-2:]
    bands = indicator(network, pool, "bbands")[-1]

    # The close of the same candle the indicators end on.
    candles = get(f"/networks/{network}/pools/{pool}/ohlcv",
                  start=START, interval=INTERVAL, limit=1000)
    close = next(c["close"] for c in candles if c["time_open"] == bands["timestamp"])

    cross = None
    if prev["histogram"] < 0 <= macd["histogram"]:
        cross = "up"
    elif prev["histogram"] > 0 >= macd["histogram"]:
        cross = "down"

    width = bands["upper"] - bands["lower"]
    return {
        "pool": label,
        "network": network,
        "candle": bands["timestamp"],
        "close": round(close, 6),
        "rsi_14": round(rsi["value"], 1),
        "macd_histogram": round(macd["histogram"], 6),
        "macd_cross": cross,
        "bbands_percent_b": round((close - bands["lower"]) / width, 2),
        "bbands_width_pct": round(width / bands["middle"] * 100, 2),
    }


def rules(s):
    """A plain baseline to compare the model against."""
    if s["rsi_14"] < 30 and s["bbands_percent_b"] < 0.05:
        return "buy"
    if s["rsi_14"] > 70 and s["bbands_percent_b"] > 0.95:
        return "sell"
    if s["macd_cross"] == "up" and s["rsi_14"] < 60:
        return "buy"
    if s["macd_cross"] == "down" and s["rsi_14"] > 40:
        return "sell"
    return "hold"


SYSTEM = """You are a paper-trading agent. For each pool in the JSON you get,
decide buy, sell or hold for the next hour, using only the fields given.
rsi_14: RSI(14), over 70 is commonly read as overbought, under 30 as oversold.
macd_histogram: MACD minus its signal line. macd_cross is "up" or "down" when
it changed sign on the last candle. bbands_percent_b: 0 at the lower Bollinger
Band, 1 at the upper. bbands_width_pct: band width as a percentage of the middle
band; narrow bands often come before a large move.
Answer with a JSON array only, one object per pool:
[{"pool": "...", "network": "...", "action": "buy|sell|hold",
  "confidence": 0.0, "reason": "one sentence"}]"""


def model(states):
    import anthropic  # pip install anthropic; reads ANTHROPIC_API_KEY

    reply = anthropic.Anthropic().messages.create(
        model="claude-sonnet-5",
        max_tokens=1024,
        system=SYSTEM,
        messages=[{"role": "user", "content": json.dumps(states)}],
    )
    return json.loads(reply.content[0].text)


def cycle(use_model):
    states = [snapshot(*p) for p in WATCHLIST]
    for s in states:
        print(json.dumps(s))
        print(f"  rules: {rules(s)}")
    if use_model:
        for d in model(states):
            print(f"  model, {d['pool']} on {d['network']}: {d['action']} "
                  f"({d['confidence']:.2f}) {d['reason']}")


if __name__ == "__main__":
    use_model = "ANTHROPIC_API_KEY" in os.environ
    while True:
        cycle(use_model)
        # Wake a minute after the next hourly candle closes.
        time.sleep(3600 - time.time() % 3600 + 60)
```

One cycle without any key, measured on 13 October 2026:

```
{"pool": "WETH/USDC, Uniswap v3", "network": "ethereum", "candle": "2026-10-13T11:00:00Z", "close": 2499.423072, "rsi_14": 46.7, "macd_histogram": 1.852675, "macd_cross": null, "bbands_percent_b": 0.5, "bbands_width_pct": 4.66}
  rules: hold
{"pool": "SOL/USDC, Orca", "network": "solana", "candle": "2026-10-13T11:00:00Z", "close": 106.168135, "rsi_14": 39.5, "macd_histogram": 0.055981, "macd_cross": null, "bbands_percent_b": 0.31, "bbands_width_pct": 4.99}
  rules: hold
{"pool": "WETH/USDC, Uniswap v3", "network": "base", "candle": "2026-10-13T11:00:00Z", "close": 2498.5663, "rsi_14": 46.7, "macd_histogram": 1.899912, "macd_cross": null, "bbands_percent_b": 0.5, "bbands_width_pct": 4.64}
  rules: hold
```

Each cycle is four requests per pool, three indicators and one OHLCV call, so twelve for this watchlist and 288 a day. The script waits out a `429` for the seconds in `Retry-After`. Check how that fits your plan on [rate limits](/knowledge-base/rate-limits).

The model step uses the Anthropic SDK as an example. Any model that returns JSON works: keep the system prompt, send the states, parse the answer.

***

## Why a free key matters here

Without a key, pool indicators cover the last 24 hours at `1h` and up. That is enough for the script to run, and every field above gets a value, but RSI, EMA and MACD are smoothed: each value carries weight from the candles before it, and 24 hourly candles are too few for RSI(14) to settle. The [technical indicators tutorial](/tutorials/technical-indicators#four-things-that-will-surprise-you) shows the same candle's RSI moving from 52.47 to 61.18 as the history behind it grows.

A [free key](https://console.dexpaprika.com) opens the last 7 days at `10m` and up. The script switches `START` to `-7d` when `DEXPAPRIKA_API_KEY` is set, which gives RSI(14) on hourly candles more than ten periods of warm-up. The key goes in the `Authorization` header on its own, and free keys stay on `api.dexpaprika.com`.

***

## Where the free tier ends

| You want | Plan |
| - | - |
| `1m` or `5m` candles, for an agent that trades on minutes | Dev |
| More than 7 days of history, to backtest the agent | Dev (30 days), Pro or Enterprise (no limit) |
| One USD series per token across every pool it trades in, through [token indicators](/api-reference/tokens/get-a-technical-indicator-for-a-token) | Dev, Pro or Enterprise |

Paid keys call `https://api-pro.dexpaprika.com`. Plans are on [pricing](https://dexpaprika.com/api/pricing).

***

## Make it yours

* **Change the watchlist.** Find pools with [pool search](/tutorials/pool-filtering) or a token's pools with [token pools](/tutorials/token-pools), and paste the network and address.
* **Add an indicator.** `indicator=ema` or `indicator=vwap` returns `value`. Send only that indicator's parameters: `period` for rsi, ema, bbands and vwap; `fast_period`, `slow_period` and `signal_period` for macd; `std_dev` for bbands. Any other parameter is refused with `400`.
* **Keep a log.** Store each state with the model's decision and the next candle's close. That log is what tells you whether the model beats the rules.

***

## Where to go next

* [Technical indicators](/tutorials/technical-indicators): every indicator, parameter and error
* Indicators reference for [pools](/api-reference/pools/get-a-technical-indicator-for-a-pool) and [tokens](/api-reference/tokens/get-a-technical-indicator-for-a-token)
* [Rate limits](/knowledge-base/rate-limits): limits by plan
* [Pricing](https://dexpaprika.com/api/pricing) and [console.dexpaprika.com](https://console.dexpaprika.com): plans and your key


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.