---
name: damo-market-state
description: DAMO market state for crypto assets — a fixed, versioned definition of market state, recorded at the time, never recomputed, verifiable by anyone. Four permanent state codes across ten fixed timeframes (1m to 1M), in two layers (VERIFIED closed candles, LIVE forming candles), from a keyless HTTP API and a remote MCP server. State data only; no prices, candles or forecasts.
license: Proprietary. Use of the DAMO API and this document is subject to the DAMO API Terms. Not open source.
---

# DAMO Market State

DAMO is a fixed, versioned definition of market state: recorded at the time, never recomputed, verifiable by anyone.
It describes the state of a crypto market across 10 fixed timeframes using exactly 4 deterministic states.
It is rule-based, not machine learning. The same closed candles always produce the same state.
DAMO provides market state data only. It is not a trading signal and not investment advice.

## Quick start

1. Connect any MCP client that supports Streamable HTTP to `https://api.damolive.com/mcp`. No account or key.

   ```
   claude mcp add --transport http damo https://api.damolive.com/mcp
   ```
   ```json
   { "mcpServers": { "damo": { "url": "https://api.damolive.com/mcp" } } }
   ```

2. Ask, for example: "What is the DAMO VERIFIED state of BTC on the 1h and 1d timeframes?"

3. The agent calls `get_market_state` with `{"symbol": "BTCUSDT", "timeframes": ["1h", "1d"]}` (2 state values). A real response, trimmed:

   ```json
   {
     "symbol": "BTCUSDT", "layer": "verified", "as_of": "2026-10-14T01:40:03.748Z",
     "vector": [null, null, null, 2, null, null, 2, null, null, null],
     "states": {
       "1h": {"state": "weakening", "state_code": 2, "candle_status": "closed", "final": true,
              "candle_close": "2026-10-14T01:00:00Z", "engine_version": "1.0",
              "seq": 29610, "hash": "2952c6acb2dd92e9…", "stale": false},
       "1d": {"state": "weakening", "state_code": 2, "candle_status": "closed", "final": true,
              "candle_close": "2026-10-14T00:00:00Z", "engine_version": "1.0",
              "seq": 29532, "hash": "924b7042d054a872…", "stale": false}
     },
     "citation": "DAMO VERIFIED · BTCUSDT · recorded state as of 2026-10-14T01:40:03.748Z · damolive.com"
   }
   ```

4. An answer that uses it cites the source line, for example:
   "BTCUSDT is in the weakening state on both 1h and 1d (DAMO VERIFIED · BTCUSDT · recorded state as of 2026-10-14T01:40:03.748Z · damolive.com)."

Without MCP, the same request over HTTP:

```
curl -s "https://api.damolive.com/v1/market-state/BTCUSDT?timeframes=1h,1d"
```

## What DAMO gives

Exactly four states. The codes are permanent and are never reassigned.

| Code | State | Color |
|---|---|---|
| 1 | bull_momentum | green |
| 2 | weakening | red |
| 3 | bear_momentum | blue |
| 4 | recovery | orange |

Timeframes, always in this order: `1m, 5m, 15m, 1h, 4h, 12h, 1d, 3d, 1w, 1M`.
A market's full state is a 10-value vector in that order, e.g. `BTCUSDT → [4,3,3,3,3,2,2,1,1,4]`.
All times are UTC.

Two layers:

- **DAMO VERIFIED** (`layer=verified`, default) — "What DAMO recorded then." The state of each closed candle, recorded once when the candle closes and never changed. Each value carries `final: true`, `candle_close`, `engine_version`, `seq` and `hash`.
- **DAMO LIVE** (`layer=live`) — "What DAMO sees now." The state of the candle still forming, with `progress` (0.0–1.0). Provisional until the candle closes. Never written into the verified record.

A timeframe can be `{"status": "not_yet_available"}` (not recorded yet for that asset) or `{"status": "data_unavailable"}`; its vector value is `null`.

## How it can be checked

- Each VERIFIED record is part of an append-only hash chain. Past periods are never back-filled; periods when the recorder was not running are recorded as gaps.
- Each UTC day gets a Merkle-root attestation linked to the previous day. Attestation hashes are anchored on Base mainnet, contract `0x67FeC2A19B3e3eb26d80Ad87D19e33dfc39F5766` (source verified on Basescan): `heads(symbol)`, `attestationOf(symbol, day)`, `isAnchored(symbol, day, attestationHash)`.
- Each VERIFIED value carries its seq and hash, so an answer that cites it can be checked later against the anchored daily attestation.

## What does not change

- The 4 state codes and the 10 timeframes are permanent.
- Any engine change is released as a new engine version; every VERIFIED value carries its `engine_version`.
- Recorded states are never recomputed.

## Not provided

- Prices or candles, order book, funding rates, intrabar events.
- Forecasts, trading signals or investment advice.
- Bulk historical exports. The full recorded history is not offered as a download.

## Access

No account or API key. Free tier: 500 state values per client per UTC day (1 state value = 1 symbol x 1 timeframe; one full 10-timeframe asset = 10 values). Every state response includes a `quota` object (`daily_limit`, `used`, `remaining`, `resets_at`). Over the quota the API returns 429 `daily_quota_exceeded` with `Retry-After`; MCP tools return `isError` with `retry_after_seconds`. Symbol lists and state definitions are not counted. Request rate limit: around 60 requests per minute per IP.

**HTTP** — base URL `https://api.damolive.com`

| Request | Returns |
|---|---|
| `GET /v1/market-state/{symbol}` | VERIFIED state of one asset |
| `GET /v1/market-state/{symbol}/live` | LIVE state of one asset |
| `GET /v1/market-state?symbols=BTCUSDT,ETHUSDT&layer=verified` | up to 50 assets |
| `GET /v1/symbols` | covered assets |
| `GET /v1/states` | state definitions |
| `GET /openapi.json` | OpenAPI spec |
| `GET /.well-known/damo-keys.json` | public keys for response signatures |

```
curl -s https://api.damolive.com/v1/market-state/BTCUSDT
```

**MCP** — Streamable HTTP at `https://api.damolive.com/mcp` (protocol 2026-07-28 with `server/discover`, and 2025-06-18 / 2025-03-26 / 2024-11-05 with `initialize`). Read-only tools:

- `get_market_state` — `symbol`, optional `layer`, `timeframes`, `format`, `proof`
- `get_market_states` — `symbols` (up to 50), optional `layer`, `timeframes`, `format`, `proof`
- `list_symbols`
- `get_state_definitions`

Tool results carry the data as `structuredContent` (and the same JSON as text). For protocol 2025-06-18 and 2026-07-28, `tools/list` also gives each tool an `outputSchema`; it fits both `full` and `compact`.

Symbols: give a ticker such as `BTCUSDT`. `BTC`, `btc`, `BTC/USDT`, `BTC-PERP` and coin names like `Bitcoin` are also accepted (the response then shows `symbol_input`). A sentence is not accepted: the error lists `did_you_mean`.

### Time fields

| Field | Layer | Meaning |
|---|---|---|
| `as_of` | verified | When DAMO last wrote the verified record (UTC). Not a candle time. |
| `states[tf].candle_close` | verified | Close time of the candle whose state was recorded for that timeframe. The state was recorded at that close and never changed. |
| `observed_at` | live | When DAMO observed the forming candles (UTC). |
| `states[tf].candle_close_expected` | live | When the forming candle will close. Until then the state is provisional. |

There is no `generated_at` in state responses. A recorded value is what DAMO computed at that candle close, not a later recomputation of the past.

### Fewer values: choose timeframes

Add `timeframes` to request only what you need; only those are counted. The `vector` keeps its 10 positions; the others are `null` with `{"status": "not_requested"}`. Timeframe names are case-sensitive: `1m` = minute, `1M` = month.

```
curl -s "https://api.damolive.com/v1/market-state/BTCUSDT?timeframes=15m,1h"      # 2 values
curl -s "https://api.damolive.com/v1/market-state?symbols=BTCUSDT,ETHUSDT&timeframes=1h"   # 2 values
```

MCP tools accept the same as an array: `"timeframes": ["15m", "1h"]`.

### Unchanged state: ETag and 304

Every HTTP state response carries an `ETag` header. Send it back as the query parameter `if_none_match=<ETag>` (the `If-None-Match` header is also accepted, but some connections, including the public api.damolive.com front end, do not pass it through). If nothing changed, the API answers `304 Not Modified` with no body, and it is **not counted** against the quota (the request rate limit still applies). VERIFIED states change only when a candle closes, so polling between closes returns 304.

### Shorter responses: format=compact

Add `format=compact` (MCP: `"format": "compact"`) to get only `symbol`, `layer`, the time (`as_of` or `observed_at`), the 10-value `vector`, a short `citation` line and `quota_remaining` (plus `"stale": true` when the data is old). It is counted the same way (state values). The default `format=full` adds per-timeframe `state`, `seq`, `hash` and `engine_version`, for citing and checking records. A `null` in the vector means not requested, not yet available or data unavailable; use `full` to see which.

```
curl -s "https://api.damolive.com/v1/market-state/BTCUSDT?timeframes=1h,1d&format=compact"
```

The compact form of the Quick start response above:

```json
{"symbol":"BTCUSDT","layer":"verified","as_of":"2026-10-14T01:40:03.748Z","vector":[null,null,null,2,null,null,2,null,null,null],"citation":"DAMO VERIFIED · BTCUSDT · as of 2026-10-14T01:40:03.748Z · damolive.com"}
```

(plus `quota_remaining`, the free quota left today).

Batch compact responses carry one `citation` line for all results.

### Signed responses

Every state response is signed by DAMO with Ed25519, so an agent can check that the values came from DAMO and were not changed on the way (for example through a proxy or another service).

- HTTP: header `DAMO-Signature: kid="…", alg="Ed25519", sig="<base64url>"`
- MCP: tool result `_meta["com.damolive/signature"] = {kid, alg, sig}`
- In the body only when asked: `proof=1` (MCP: `"proof": true`) adds `"proof": {kid, alg, sig}`
- Public keys (JWK): `https://api.damolive.com/.well-known/damo-keys.json`

What is signed: the state object restricted to the fields `symbol`, `layer`, `as_of`, `observed_at`, `vector_order`, `vector`, `states`, `engine_version`, `chain_head` that are present in it (batch: `{"results": [...]}` of those objects; `errors` are not signed), serialized with RFC 8785 (JCS). `quota`, `citation`, `proof` and other fields are not signed.

```python
import base64, json, urllib.request
import rfc8785                                    # pip install rfc8785 cryptography
from cryptography.hazmat.primitives.asymmetric.ed25519 import Ed25519PublicKey

b64 = lambda s: base64.urlsafe_b64decode(s + "=" * (-len(s) % 4))
FIELDS = ("symbol", "layer", "as_of", "observed_at", "vector_order", "vector", "states", "engine_version",
          "chain_head")
keys = json.load(urllib.request.urlopen("https://api.damolive.com/.well-known/damo-keys.json"))["keys"]

with urllib.request.urlopen("https://api.damolive.com/v1/market-state/BTCUSDT?format=compact") as r:
    body = json.load(r)
    sig = dict(p.strip().split("=", 1) for p in r.headers["DAMO-Signature"].split(","))
sig = {k: v.strip('"') for k, v in sig.items()}
pick = lambda d: {k: d[k] for k in FIELDS if k in d}
signed = {"results": [pick(x) for x in body["results"]]} if "results" in body else pick(body)
key = next(k for k in keys if k["kid"] == sig["kid"])
Ed25519PublicKey.from_public_bytes(b64(key["x"])).verify(b64(sig["sig"]), rfc8785.dumps(signed))   # raises if invalid
print("signed by DAMO", sig["kid"])
```

The signature shows who sent the values. That a VERIFIED value was recorded at the time and never changed is shown separately by its `seq` and `hash` and the daily attestation anchored on Base mainnet.

### Budget example

| Polling | Values per day |
|---|---|
| 1 asset, all 10 timeframes, every 15 min | 960 (over the free tier) |
| 1 asset, `timeframes=15m`, every 15 min | 96 |
| 5 assets, `timeframes=1h`, every hour | 120 |
| any of the above, repeated polls answered with 304 | not counted |

Symbols use common USDT-perpetual tickers (`BTCUSDT`, `1000PEPEUSDT`). `btc`, `BTC`, `BTCUSDT.P` are normalized to `BTCUSDT`.

## Example: poll after candle close, with cache and ETag (Python)

Prints the state only. No trading logic.

```python
import time, urllib.request, urllib.error, urllib.parse, json

URL = "https://api.damolive.com/v1/market-state?symbols=BTCUSDT,ETHUSDT&timeframes=15m"
TF_SECONDS = 15 * 60
etag, last = None, None

while True:
    url = URL + ("&if_none_match=" + urllib.parse.quote(etag) if etag else "")
    req = urllib.request.Request(url)
    try:
        with urllib.request.urlopen(req, timeout=10) as r:
            etag, last = r.headers.get("ETag"), json.load(r)
            for s in last["results"]:
                print(s["symbol"], s["states"]["15m"].get("state"), s["citation"])
            print("remaining today:", last["quota"]["remaining"])
    except urllib.error.HTTPError as e:
        if e.code == 304:
            pass                      # unchanged, not counted; keep using `last`
        elif e.code == 429:
            wait = int(e.headers.get("Retry-After", "60"))
            time.sleep(wait)          # keep using `last` (VERIFIED values are final)
            continue
        else:
            raise
    now = time.time()
    time.sleep(TF_SECONDS - now % TF_SECONDS + 5)   # just after the next 15m close (UTC)
```

The response fields are `results[]` (batch) or the state object (single symbol): `symbol`, `layer`, `vector_order`, `vector`, `states{tf: {state, state_code, final, candle_close, engine_version, seq, hash}}`, `citation`, `source_url`, `quota`.

## Citing DAMO data

Every state response carries a `citation` line and a `source_url`, for example:

```
"citation": "DAMO VERIFIED · BTCUSDT · recorded state as of 2026-10-11T04:00:01.000Z · damolive.com"
"citation": "DAMO LIVE (provisional, forming candles) · BTCUSDT · observed 2026-10-11T04:02:13.000Z · damolive.com"
```

LIVE values are provisional and are not recorded history.

## Coverage and source

- Assets and selection criteria: https://damolive.com/coverage. Removing an asset never erases its records.
- Source: the DAMO composite, built from several public crypto perpetual futures markets. DAMO publishes its own state outputs only, not raw prices or candles.
- VERIFIED records with `engine_version` 1.0 (before 2026-10-26 00:00 UTC) were computed from a single reference market; engine 1.1 and later use the DAMO composite. Same calculation, new input; old records are never recomputed.
- Recording for QNTUSDT stopped at 2026-10-26 00:00 UTC (`symbol_retired`). Its past records and attestations remain verifiable.
- DAMO does not claim that its states predict prices.

More: https://damolive.com/agents · https://damolive.com/llms.txt · Terms: https://damolive.com/api-terms · Privacy: https://damolive.com/privacy
