Metadata-Version: 2.4
Name: kresmion
Version: 0.2.1
Summary: Official Python SDK for the Kresmion financial-intelligence API
Author-email: Kresmion <solalguillemois@gmail.com>
License: MIT
Project-URL: Homepage, https://kresmion.com
Project-URL: Documentation, https://kresmion.com/developers
Project-URL: Download, https://kresmion.com/sdk/kresmion.py
Keywords: kresmion,finance,prediction-markets,crypto,equities,macro,api,sdk
Classifier: Development Status :: 4 - Beta
Classifier: Intended Audience :: Developers
Classifier: Intended Audience :: Financial and Insurance Industry
Classifier: License :: OSI Approved :: MIT License
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3 :: Only
Classifier: Topic :: Office/Business :: Financial :: Investment
Requires-Python: >=3.9
Description-Content-Type: text/markdown
License-File: LICENSE
Requires-Dist: requests>=2.28
Dynamic: license-file

# Kresmion Python SDK

Official Python client for the [Kresmion](https://kresmion.com) financial
intelligence API: prediction markets, on-chain crypto flows, equities
signals, macro regime, cross-market signals, and bulk historical data.

The SDK targets the versioned `/v1` API. It depends only on the Python
standard library plus [`requests`](https://pypi.org/project/requests/).
It returns data for you to work with; it never returns advice.

Version 0.2.1. Python 3.9 and newer.

## Install

Two ways, pick one.

**From PyPI:**

```
pip install kresmion
```

**Single file download (no packaging):**

```bash
curl -O https://kresmion.com/sdk/kresmion.py
pip install "requests>=2.28"
```

Drop `kresmion.py` next to your script and `import kresmion` works.

**Install from source (pip, from a git checkout):**

```bash
pip install "git+https://github.com/kresmion/kresmion-python.git#subdirectory=sdk/python"
# or, from a local clone of the repo:
pip install ./sdk/python
```

Both forms expose the identical `kresmion` module. The single file is a
verbatim build of the package (`build_singlefile.py` keeps them in sync).

## Quickstart

```python
from kresmion import Kresmion

# api_key falls back to the KRESMION_API_KEY environment variable.
k = Kresmion(api_key="krm_your_key_here")

markets = k.prediction.markets(category="crypto", limit=10)
for m in markets:
    print(m["question"], m.get("yes_probability"))

# List results are a plain list plus .count and .as_of metadata.
print("returned", len(markets), "of", markets.count, "as of", markets.as_of)

# Single-object results are a plain dict plus .as_of.
regime = k.macro.regime()
print(regime["bucket"], "as of", regime.as_of)
```

Every list method returns an `ApiList` (a `list` subclass carrying
`.count` and `.as_of`). Every single-object method returns an `ApiDict`
(a `dict` subclass carrying `.as_of`). You can treat them as ordinary
`list` / `dict` and reach for the metadata attributes when you need them.

## Compound brief (start here)

`k.brief(asset_class, symbol)` is the fastest way in: one call assembles
the full cross-product digest for a single symbol from every relevant
product family, so you do not have to stitch the family endpoints
together yourself.

```python
btc = k.brief("crypto", "BTC")
# crypto: derivatives + cross-exchange funding, liquidations, on-chain /
# stablecoin flows, ETF flows, related prediction markets, macro regime.

nvda = k.brief("equity", "NVDA")
# equity: signals, insider + congress, 13F, modeled dealer gamma, related
# prediction markets.
```

Each sub-section carries its own `as_of`, so you can tell which legs are
fresh and which are stale. The digest is descriptive and event-framed,
never advice. Reach for the family methods below when you need to drill
deeper than the digest returns.

## Configuration

```python
k = Kresmion(
    api_key=None,                      # else env KRESMION_API_KEY
    base_url="https://kresmion.com/api",
    timeout=30,                        # seconds per request
    max_retries=3,                     # extra attempts on 429 / 5xx / connection error
)
```

The client keeps a persistent HTTP session for connection reuse. Use it
as a context manager to close the session automatically:

```python
with Kresmion() as k:
    print(k.status())
```

## Namespaces

One example per namespace. Every method has a full docstring, so
`help(k.prediction.markets)` is your inline reference.

### `k.prediction` - prediction markets

```python
k.prediction.markets(category="crypto", min_volume=10000, q="fed", limit=50)
k.prediction.market("0xabc123")                  # one market
k.prediction.history("0xabc123", days=7)         # probability history
k.prediction.book("0xabc123")                    # order book
k.prediction.book_history("0xabc123", hours=24)  # depth/spread over time
k.prediction.execution_cost("0xabc123", notionals=[100, 1000, 10000])  # one-leg fill cost
k.prediction.algo_flags("0xabc123")              # algorithmic-flow flags
k.prediction.flow("0xabc123")                    # recent flow
k.prediction.movers(limit=25)
k.prediction.consensus()
k.prediction.divergence(executable_size=1000)    # cross-venue gaps, netted vs Polymarket-leg cost at $1k
k.prediction.options_divergence(currency="BTC", min_abs_spread=0.05)  # Polymarket vs Deribit options-implied spread
k.prediction.options_divergence_history("0xabc123", days=30)          # that spread over time
k.prediction.calendar(days=14)                   # upcoming resolutions (scheduled dates)
k.prediction.calibration()
k.prediction.resolutions(limit=25)
```

### `k.crypto` - on-chain flows, ETFs, derivatives, options

```python
whales = k.crypto.whales(min_usd=1_000_000, hours=24)
k.crypto.exchange_holdings()
k.crypto.exchange_history("binance", days=30)
k.crypto.etf_flows()
k.crypto.etf_history("IBIT", days=30)
k.crypto.derivatives("BTC")
k.crypto.funding()                    # cross-exchange funding surface
k.crypto.funding("BTC")               # one symbol's funding history
k.crypto.liquidations("BTC", hours=24)  # forced deleveraging (single venue, OKX)
k.crypto.stablecoins()                # aggregate stablecoin supply / issuance
k.crypto.unlocks(days=90)             # forward token-unlock calendar
k.crypto.funding_history("BTC", days=30)
k.crypto.oi_history("BTC", days=30)
k.crypto.options("BTC")
```

### `k.equities` - signals, insiders, congress, 13F

```python
k.equities.signals(limit=50)
k.equities.insider_clusters(limit=50)
k.equities.congress(limit=50)
k.equities.institutional(limit=50)
k.equities.price_history("AAPL", days=90)
k.equities.gamma()               # modeled dealer-gamma surface
k.equities.gamma("NVDA")         # one ticker: gamma by strike, flip level, walls
k.equities.smart_money(min_families=2, direction="long")  # per-ticker positioning composite, legs listed
k.equities.smart_money_ticker("NVDA")                     # one ticker: live composite + snapshot trend
```

### `k.macro` - regime, COT, TIC, BIS

```python
k.macro.regime()                    # current cross-asset Risk-On/Off score and factors
k.macro.regime_history(days=365)    # the regime score time series
k.macro.correlations()              # cross-asset correlation breaks (latest snapshot)
k.macro.cot()
k.macro.tic()
k.macro.bis()
```

### `k.signals` - cross-market signal feed

The signals namespace is callable, and it carries an `accuracy` method:

```python
feed = k.signals(limit=50, severity="high")   # GET /v1/signals
stats = k.signals.accuracy()                  # GET /v1/signals/accuracy
```

### `k.track_record` - signal forward-return track record

How the live production signals were actually followed by price at 1, 7,
and 30 days, unfiltered, so losing signal types show their negative
medians too. Entry is the next daily close after the signal day.

```python
k.track_record.aggregates(asset_class="crypto", min_n=5)  # per-type/direction aggregates
k.track_record.types()                                    # which detectors have history
k.track_record.for_type("funding_stress", symbol="BTC")   # per-symbol + recent occurrences
```

### `k.confluence` - cross-family confluence + divergence

Symbols where several independent signal families line up on a direction
(confluence) or strongly disagree (divergence), each contributing leg
listed under `legs`.

```python
k.confluence.list(conviction="HIGH", hours=48)   # active composites, newest first
k.confluence.for_symbol("BTC", hours=168)         # current + history for one symbol
```

### `k.billing` - paid quota top-ups (Solana USDC)

```python
plans = k.billing.plans()             # catalog: plan, usdc price, calls, days
q = k.billing.quote("boost_100k")     # -> payment_id, amount_usdc, recipient, reference, ...
st = k.billing.status(q["payment_id"])  # poll: pending -> confirmed -> credited
```

See "Paying for quota with Solana USDC" below for the full flow.

### `k.status()` and `k.openapi()`

```python
k.status()      # service status and your quota snapshot
k.openapi()     # the raw OpenAPI 3 document for /v1
```

### `k.changes(...)` - cross-family delta feed for polling

One poll returns only the rows in each product family that are newer than
your `since` cursor, so a running agent stays current for a tiny token
cost instead of re-fetching whole family payloads. The endpoint holds no
server-side cursor: the returned dict's `.as_of` is the next cursor (it
equals `meta.next_since`), so pass it straight back as `since`. Valid
families: `signals`, `whale`, `prediction`, `funding`, `etf_flow`,
`gamma`, `confluence`. `since` is capped at 7 days old.

```python
snap = k.changes(since="2026-07-17T00:00:00Z", families=["signals", "whale"])
for sig in snap["signals"]:
    print(sig["asset_symbol"], sig["signal_type"])
snap = k.changes(since=snap.as_of, families=["signals", "whale"])  # next poll
```

### `k.bulk` - bulk historical downloads

Streams a gzip-compressed CSV for a date range straight to disk, with an
optional progress callback:

```python
import gzip, csv

def show(done, total):
    print(done, "/", total, "bytes")

k.bulk.download("prediction_markets", "2026-01-01", "2026-06-30",
                "pm.csv.gz", progress=show)

with gzip.open("pm.csv.gz", "rt") as fh:
    for row in csv.DictReader(fh):
        print(row)
```

## Webhook verification

Kresmion signs every webhook delivery with HMAC-SHA256 over the raw
request body and sends the result in the `X-Kresmion-Signature` header as
`sha256=<hex>`. Verify it with the shared secret you received when you
created the webhook. Always hash the exact raw bytes, never re-serialized
JSON.

```python
from kresmion import verify_webhook

# secret: the value handed to you once at webhook-create time.
# raw:    the exact request body bytes.
# sig:    the X-Kresmion-Signature request header.
if verify_webhook(secret, raw, sig):
    handle(raw)
else:
    reject()  # 400
```

`verify_webhook` does a constant-time comparison and returns `False`
(never raises) on a bad or missing signature. See
`examples/webhook_receiver.py` for a complete stdlib receiver.

### Global market events

Subscribe a webhook to any of these global market events (envelope
`{"event", "fired_at", "data"}`). The authoritative catalog, with an
example body and expected cadence per event, is served live at
`GET /v1/webhooks/events`.

| Event | Fires when |
| --- | --- |
| `signal.created` | A new cross-asset signal fires at the top (critical) severity tier. |
| `whale.transfer` | A single on-chain transfer >= $1M (self-transfer artifacts excluded). |
| `prediction.repricing` | A market (>= $50k volume) whose YES probability moved >= 10pp in 24h. |
| `prediction.divergence` | A linked Polymarket/Kalshi pair whose cross-venue \|spread\| crossed >= 5pp. |
| `prediction.resolution_imminent` | A contested market (>= $50k volume) whose scheduled close is within 48h. |
| `prediction.algo_flow` | A high-conviction algorithmic-flow flag on a market (confidence >= 85, notional >= $25k). |
| `etf.flow` | A new daily crypto-ETF flow print (once per new date per asset). |
| `regime.change` | The daily macro Risk-On/Off regime crossed into a new bucket. |

## Rate limits and error handling

Keyed responses carry rate-limit headers. After every call, the client
refreshes `k.last_rate_status`:

```python
k.prediction.markets(limit=1)
print(k.last_rate_status)
# {'limit_minute': 60, 'remaining_minute': 59,
#  'monthly_limit': 10000, 'monthly_used': 42}
```

The client retries transient failures for you with exponential backoff
and jitter, up to `max_retries`:

- On `429`, it honors the `Retry-After` header exactly when the server
  sends one, otherwise it backs off exponentially.
- On `5xx` and connection errors, it backs off exponentially.

When retries are exhausted, or on an error that is not retried, the
client raises a typed exception:

| Exception | Raised on | Carries |
| --- | --- | --- |
| `KresmionAuthError` | `401` (not retried) | `.status`, `.detail` |
| `KresmionRateLimitError` | `429` after retries | `.retry_after`, `.rate_status` |
| `KresmionAPIError` | other `4xx` / `5xx`, or connection failure | `.status`, `.detail` |

All three subclass `KresmionError`, so you can catch broadly or narrowly:

```python
from kresmion import (
    KresmionAuthError, KresmionRateLimitError, KresmionAPIError, KresmionError,
)

try:
    data = k.crypto.whales(min_usd=5_000_000)
except KresmionRateLimitError as exc:
    print("slow down for", exc.retry_after, "seconds")
except KresmionAuthError:
    print("check your API key")
except KresmionAPIError as exc:
    print("api error", exc.status, exc.detail)
except KresmionError as exc:
    print("kresmion error", exc)
```

## Paying for quota with Solana USDC

When your key exhausts its monthly quota (a `KresmionRateLimitError` whose
detail names the monthly limit, not the per-minute window), you can buy more
calls by paying USDC on Solana mainnet. Four steps:

```python
import time

plans = k.billing.plans()               # 1. catalog: plan, usdc price, calls, days
q = k.billing.quote("boost_100k")       # 2. create a payment for a plan

# 3. Send EXACTLY q["amount_usdc"] USDC (full 6-decimal precision, e.g.
#    5.000731; SPL mint EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v) to
#    q["recipient"] on Solana mainnet, from any wallet or exchange. The exact
#    amount is what identifies your payment; including q["reference"] as an
#    extra account is optional and merely speeds attribution. Then:

while k.billing.status(q["payment_id"])["status"] != "credited":  # 4. poll
    time.sleep(5)
```

The boosted quota applies to the key that created the quote the moment its
status reads `credited`. Payments are non-refundable, a quote expires 30
minutes after it is created, and only USDC on Solana mainnet is accepted.

One honest note: this SDK does not sign or send the transfer. You send it
yourself from any wallet or exchange, or programmatically with a Solana wallet
library such as [`solders`](https://pypi.org/project/solders/) or
[`solana`](https://pypi.org/project/solana/) (solana-py). The exact quoted
amount is what matches the transfer to your quote; the `reference` pubkey is
optional and only speeds attribution. Kresmion never holds your keys.

## Examples

- `examples/divergence_monitor.py` polls prediction divergence every 60
  seconds and prints crossings.
- `examples/webhook_receiver.py` is a stdlib `http.server` receiver that
  verifies signatures.
- `examples/backtest_download.py` bulk-fetches a dataset and parses the
  gzip CSV into rows.

## License

MIT.
