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

# Price & Yield

> Cached 0G/USD price feed plus the live Gimo APY derived from a rolling rate-sample window.

## GET /price/0g

```bash theme={null}
curl https://api.signalium.site/price/0g
```

Response:

```json theme={null}
{
  "symbol": "0g",
  "priceUsd": 0.557,
  "change24h": 0.182,
  "source": "coingecko",
  "fetchedAt": 1777454139365
}
```

### How it works

* Upstream: CoinGecko `/simple/price?ids=zero-gravity&vs_currencies=usd&include_24hr_change=true`.
* Cache: SQLite row keyed by `0g`, refreshed when older than 6 hours.
* Stale fallback: if CoinGecko is unreachable but a cached row exists, the cached value is served and a warning is logged. Total upstream failures (cold cache + outage) return `502`.

The web app uses this for the `≈ $X.XX` display in the bet panel via the [`use0gPrice`](https://github.com/rstfulzz/signalium/blob/main/apps/web/src/hooks/usePrice.ts) hook.

## GET /price/og-yield

```bash theme={null}
curl 'https://api.signalium.site/price/og-yield?windowDays=7'
```

Response:

```json theme={null}
{
  "apyBps": 487,
  "sampleCount": 168,
  "windowFromUnix": 1777022139,
  "latestRateWei": "1281640000000000000",
  "latestSampledAt": 1777626739,
  "fallbackApyBps": 500
}
```

| Field             | Meaning                                                                                                                                                                                      |
| ----------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `apyBps`          | Annualized return in basis points, derived from the slope between the oldest and newest samples in the window. **Null** until ≥2 samples spanning ≥1 hour exist (\~2h after a fresh deploy). |
| `sampleCount`     | How many samples fell inside the window.                                                                                                                                                     |
| `windowFromUnix`  | Window start (unix seconds) — `now − windowDays`.                                                                                                                                            |
| `latestRateWei`   | Most recent `getRate()` reading (1e18-scaled), useful for sanity-checking.                                                                                                                   |
| `latestSampledAt` | Unix timestamp of that latest sample.                                                                                                                                                        |
| `fallbackApyBps`  | Default to use when `apyBps` is null (currently 500 = 5%).                                                                                                                                   |

### How it works

* An hourly sampler ([`gimo-sampler.ts`](https://github.com/rstfulzz/signalium/blob/main/apps/api/src/services/gimo-sampler.ts)) reads `StakePool.getRate()` from 0G mainnet via viem and persists `(sampledAt, rateWei)` rows into the `gimo_rate_sample` table.
* On startup, if the previous process died more than one interval ago, the sampler immediately backfills a sample so the series stays continuous.
* [`estimateYield`](https://github.com/rstfulzz/signalium/blob/main/apps/api/src/services/gimo-yield.ts) computes `APR = (r1/r0 − 1) × (yearSeconds / dt)` between the first and last sample in the window. A negative or absurd (>100%) result is treated as noise and returned as null.

The web app consumes this in [`useMarketYield`](https://github.com/rstfulzz/signalium/blob/main/apps/web/src/hooks/useMarketYield.ts) so the "projected additional yield" line in the bet panel reflects the real Gimo rate trajectory rather than a hardcoded guess. When `apyBps` is null the projection silently falls back to `fallbackApyBps`.

### Choosing `windowDays`

`st0G`'s rate moves slowly, so a wider window smooths sampler jitter at the cost of reacting to recent rate changes more slowly. The default 7 days is a good balance for a UI projection; pass `windowDays=1` for a more reactive (but noisier) reading.
