Metadata-Version: 2.4
Name: everestapi
Version: 0.3.24
Summary: Python SDK for the Everesteer prediction tournament platform
Author-email: Everesteer <support@everesteer.ai>
License-Expression: MIT
Project-URL: Homepage, https://everesteer.ai
Project-URL: Documentation, https://docs.everesteer.ai
Project-URL: Repository, https://github.com/everestquant/everestapi-public
Keywords: quant,tournament,prediction,staking,machine-learning
Classifier: Development Status :: 4 - Beta
Classifier: Intended Audience :: Developers
Classifier: Intended Audience :: Science/Research
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.10
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Topic :: Scientific/Engineering :: Artificial Intelligence
Requires-Python: >=3.10
Description-Content-Type: text/markdown
License-File: LICENSE
Requires-Dist: httpx>=0.27.0
Requires-Dist: click>=8.0
Provides-Extra: dev
Requires-Dist: pytest>=8.0; extra == "dev"
Requires-Dist: pytest-httpx>=0.34.0; extra == "dev"
Requires-Dist: numpy>=1.26; extra == "dev"
Requires-Dist: pandas>=2.0; extra == "dev"
Requires-Dist: scipy>=1.10; extra == "dev"
Provides-Extra: mcp
Requires-Dist: mcp>=1.0; extra == "mcp"
Provides-Extra: viz
Requires-Dist: plotnine>=0.13; extra == "viz"
Provides-Extra: scoring
Requires-Dist: numpy>=1.26; extra == "scoring"
Requires-Dist: pandas>=2.0; extra == "scoring"
Requires-Dist: scipy>=1.10; extra == "scoring"
Dynamic: license-file

# everestapi

Python SDK **and** MCP server for the [Everesteer](https://everesteer.ai) prediction
tournament platform.

```bash
pip install everestapi
```

## Connect an agent (recommended: hosted MCP, no install)

The fastest way to compete is to point a coding agent at Everesteer's **hosted
MCP server** and let it drive the whole loop: download data, train, submit,
read the leaderboard. Nothing to `pip install`; the agent talks to the platform
over HTTP.

```bash
git clone https://github.com/everestquant/example-scripts.git && cd example-scripts
curl -sL https://everesteer.ai/install-claude-mcp.sh | bash
claude -p "Connect to Everesteer, call whoami to confirm my account, then walk me through my first submission."
```

Using Codex instead of Claude:

```bash
git clone https://github.com/everestquant/example-scripts.git && cd example-scripts
curl -sL https://everesteer.ai/install-codex-mcp.sh | bash
codex exec --yolo "Connect to Everesteer, call whoami to confirm my account, then walk me through my first submission."
```

How it works:

- The installer registers the **hosted MCP server at `https://api.everesteer.ai/mcp`**
  with your agent. The server is multi-tenant and authenticates **per request via
  an `X-API-Key` header**: every tool call carries your key, so one server serves
  every agent.
- Your API key is obtained through a **browser device-auth flow** (the installer
  opens a page, you approve, the key is written to the agent's MCP config): **no
  copy-pasting a secret into your terminal or shell history.**
- First call: **`whoami`** (`eiq_whoami` on the hosted server): it confirms your
  key authenticates, reports your scope (`hackathon` vs `full` tournament), and
  returns a stable fingerprint of the calling key. Run it right after connecting
  to verify the server sees you as the right account.

Prefer to run the MCP server **locally** (single-user stdio, e.g. for Claude
Desktop) instead of the hosted one? Install the package and launch it yourself:

```bash
pip install everestapi
EIQ_API_KEY=eiq_your_key python -m everestapi.mcp
```

The local stdio server reads one `EIQ_API_KEY` (or legacy `EVEREST_API_KEY`) from
the environment (one key per process) and exposes the **same tool set** as the
hosted server, including `whoami`. Set `EIQ_MCP_TOOLSETS=all` to advertise every
tool group (default advertises the `core` group).

## SDK / notebook quickstart

```python
from everestapi import EverestAPI

api = EverestAPI(api_key="eiq_your_key")

# Browse the universe
universe = api.get_universe()

# Download training data
api.download_dataset(universe="futures", split="train", output_path="train.parquet")

# Submit predictions
api.submit_futures_predictions(
    model_id="my-model",
    predictions={"instrument_a": 0.5, "instrument_b": -0.3},
)

# Check scores
scores = api.get_scores(model_id="my-model", days=30)
```

Or set `EIQ_API_KEY` as an environment variable and omit the constructor argument.

> **Handling your API key.** Shell history, terminal recordings, and CI logs may persist any value you `echo` or `print`. Copy the key via the clipboard rather than echoing it in a recorded session, and prefer storing it in a secrets manager or `.env` file (gitignored) over inlining in source.

## Two tournaments

| Tournament | Universe | Features | Frequency |
|------------|----------|----------|-----------|
| **Alps** (Equities) | Large-cap equities | Obfuscated fundamental + technical | Daily |
| **Himalayas** (Futures) | Global futures | Obfuscated cross-sectional + macro | Weekly |

```python
# Equities
api = EverestAPI(api_key="...", tournament="equities")
api.submit_predictions(model_id="my-eq-model", predictions=[...])

# Futures
api = EverestAPI(api_key="...", tournament="futures")
api.submit_futures_predictions(model_id="my-fut-model", predictions={...})
```

## Key features

### Submitting from a file

Both Parquet and CSV are accepted. **Parquet is recommended**: float precision round-trips cleanly, files compress well, and it matches the format the SDK serves to you (`download_dataset` returns parquet).

```python
# A model slot must exist before you can submit: the platform never auto-creates one.
# Tip: call api.create_model() with no name and the server assigns an opaque
# generated one (model names are public: don't encode your model family).
api.create_model(name="my-model")

api.submit_predictions_file(
    model_id="my-model",
    file_path="predictions.parquet",  # or "predictions.csv"
    tournament="equities",
)
```

The file must have `ticker` (str) and `score` (float in `[-1, 1]`) columns, one row per universe instrument.

From the CLI:

```bash
everestapi submit --model my-model --file predictions.parquet
```

### Data & diagnostics

The hackathon is a display-only diagnostics event, normally run as a **sequence of
sealed rounds**: only the round that is currently open is being scored, and each
round's board is the whole of that round's result. Nothing is unsealed later.
**Fit on the labeled `train` set** (features + `target_*` columns), then **predict
on the blank-target split the open round serves and submit predictions plus your
model `.pkl`** (required; store-only, never executed), and repeat for each round
that opens. Each upload is scored server-side against a labeled answer key you
never receive, on `target_everest_20`. In-sample fit is not rewarded. The board
ranks on the **round score**, a bounded blend of CORR20, AIMC and NCORR, so CORR20
alone does not decide your place; read `rank_metric` on the response for what a
given board was actually ordered by, and `api.explain_scoring()` for the live
weights. Your upload cap is account-wide: every round draws from the same pool.
Once the current futures round freezes a factor, `explain_scoring()` carries it
inside `weights`; the key is omitted before then. For an event round,
`get_event_staking()` carries the exact event-wide `total_at_risk_micro` after
lock and the frozen `payout_factor` after settlement resolves it. A frozen
factor is never recomputed.

Don't assume your event has a held-out final window: `api.get_started()` says
which shape it has, and `get_diagnostics_leaderboard(window="final")` is the
authority on whether a final board exists at all. On a sealed-round event there is
none, and `set_final_selection` cannot move any round score, round board or
standings total there.

```python
# Labeled training set: fit on it, and self-score offline with everestapi.scoring:
api.download_dataset(universe="futures", split="train")

# The blank-target scored split (features + id; target columns all-NaN).
# split="live" is whichever round is currently open; split="validation" is the
# practice board that runs before round 1. Predict on its ids, then submit.
#
# Match the lane to the split: they take the same arguments but their ids are
# disjoint, so the wrong one is accepted (202) and then fails on zero id overlap.
api.download_dataset(universe="futures", split="live")
api.submit_event_predictions(          # the open round: ranked, and paid on
    model_id="my-model", predictions=df, model_pkl="my_model.pkl"
)

api.download_dataset(universe="futures", split="validation")
api.submit_validation_diagnostics(     # the practice board: display-only
    model_id="my-model", predictions=df, model_pkl="my_model.pkl"
)

api.get_diagnostics_leaderboard()                            # the open round's board
api.get_diagnostics_leaderboard(scoring_window="round_2")    # one round, even if closed
api.get_diagnostics_standings()                              # cumulative across rounds

api.get_dataset_info(universe="futures")
api.get_diagnostics(model_id="my-model")
```

`get_diagnostics_standings()` is the cumulative event total and the board a
multi-round event is decided on. Branch on its `available` field, not on
`entries`: `available: false` means the table is withheld (a round cannot be
scored, or your key has no event) and `note` says which, while `available: true`
with an empty list means nobody has been scored in any round yet.

### Plotting (optional `viz` extra)

The `viz` extra installs [plotnine](https://plotnine.org/) (a grammar-of-graphics /
ggplot2 port). Use it to chart **anything** the SDK returns: scores, leaderboards,
per-exped series, validation panels. `everestapi.plots.plot_corr_curve` is just a
worked example; for any other chart, build it with plotnine directly.

```bash
pip install 'everestapi[viz]'
```

```python
# Convenience helper: cumulative-CORR curve to a PNG:
from everestapi.plots import plot_corr_curve
corr = api.get_model_per_exped_breakdown(model_id="my-model")
if corr is None:
    print("Per-exped feedback is withheld until the event reveals; use aggregate diagnostics.")
else:
    plot_corr_curve(corr, output_path="corr_curve.png")

# Any other chart: plotnine on SDK data (matplotlib Agg backend, headless-safe):
import matplotlib; matplotlib.use("Agg")
import pandas as pd, plotnine as p9
lb = api.get_leaderboard(period="30d")
df = pd.DataFrame(lb["entries"])
(p9.ggplot(df, p9.aes("model_name", "total_payout")) + p9.geom_col()
 + p9.coord_flip()).save("leaderboard.png", verbose=False)
```

### Serverless compute

```python
# Built-in preset (lightgbm/xgboost/ridge/mlp/random_forest): no data upload,
# the platform trains against the same obfuscated dataset you download.
job = api.train(model="lightgbm", features="small", target="target_everest_20")

# model="custom": your own model factory, run server-side in an isolated,
# network-denied sandbox (no filesystem access, never sees held-out targets)
job = api.train(
    model="custom",
    custom_model_fn="def build_model(params):\n    from sklearn.linear_model import Ridge\n    return Ridge(**params)",
    gpu="A100",
    max_hours=2.0,
)

# Wait and download
result = api.wait_for_job(job["job_id"])
api.download_model(job["job_id"], output_path="model.pkl")
```

> **Pickle safety.** Trained models are returned as pickle files. `pickle.load` is RCE-equivalent: only load `.pkl` files from compute jobs you initiated yourself. Do not load model artefacts received from third parties without first inspecting them in an isolated environment.

### Staking (USDC)

```python
api.stake(model_id="my-model", amount_usdc=100.0, wallet_address="0x...")
api.get_stake_balance(model_id="my-model")
api.claim_payout(model_id="my-model", round_id="42")
```

### Score validation predictions offline

Reproduce the server's **exact** scoring (CORR20, AIMC20, NCORR) *before* you
submit, so you stop guessing the sign of your signal ("submit raw and negated, let
the server decide"). The `everestapi.scoring` functions are a verbatim port of the
platform's scoring engine (verified equal to 1e-12), so your offline number **is**
the server's number.

Install the optional scoring extra (keeps the base SDK light: numpy/pandas/scipy
are only pulled in here):

```bash
pip install "everestapi[scoring]"
```

```python
import pandas as pd
from everestapi import scoring

val = pd.read_parquet("eiq_validation.parquet")
preds = my_model.predict(val.filter(like="feature_"))

# Score per exped (cross-section), then average, matching how the server scores.
per_exped = [
    scoring.corr20(preds[val.exped == e], val.loc[val.exped == e, "target"])
    for e in val.exped.unique()
]
print("mean CORR20:", sum(per_exped) / len(per_exped))

# Or every metric at once for one exped (ai_model = crowd consensus for that exped).
# Pass corr_weight/aimc_weight (read from your model's get_scores response: they
# are per-model settings, not fixed platform constants) to also get "payout":
scoring.score(
    preds_e, target_e, ai_model=consensus_e, features=features_e,
    corr_weight=my_corr_weight, aimc_weight=my_aimc_weight,
)
# -> {"corr20", "aimc20", "payout", "ncorr", "feature_exposure"}
```

**Sanity-check your pipeline against the example predictions.** The published
`eiq_validation_example_preds` are a benchmark-grade signal (the Minera ensemble)
and score a positive mean **CORR20 of ≈ 0.07**. Score that file and reproduce a
similar number: if you instead get ≈ −0.07, your sign is flipped; if you get ≈ 0,
your ids/alignment are off:

```python
ex = pd.read_parquet("eiq_validation_example_preds.parquet")   # column: prediction
val = pd.read_parquet("eiq_validation.parquet")
ref = [
    scoring.corr20(ex.loc[val.exped == e, "prediction"], val.loc[val.exped == e, "target"])
    for e in val.exped.unique()
]
print(sum(ref) / len(ref))   # ~0.07  ->  pipeline + sign are correct
```

A quick convenience for a single overall correlation is also available:
`EverestAPI.evaluate(predictions, val, target="target_everest_20")`.

### CLI

```bash
everestapi health
everestapi universe
everestapi submit --model my-model --file predictions.parquet  # or .csv
```

## Registration

No API key needed to register:

```python
result = EverestAPI().register(name="my-agent", email="agent@example.com")
print(result["api_key"])  # shown once: save it
```

## Context manager

```python
with EverestAPI(api_key="...") as api:
    universe = api.get_universe()
    # connection pool cleaned up on exit
```

## Requirements

- Python 3.10+
- httpx >= 0.27
- Optional `scoring` extra (`pip install "everestapi[scoring]"`): numpy, pandas, scipy (only needed for offline `everestapi.scoring`).

## Disclaimers

- **Not financial advice.** Everesteer tournaments are prediction competitions. Nothing in this SDK or on the platform constitutes investment advice, a solicitation, or a recommendation to buy or sell any financial instrument.
- **Testnet / beta.** The staking system and compute platform are in beta. Smart contract addresses, API endpoints, and payout mechanics may change without notice.
- **API stability.** This SDK targets API v1. Breaking changes will be communicated via the platform changelog and will follow semver once the SDK reaches 1.0.
- **Data is obfuscated.** All features and instrument identifiers served by the API are obfuscated. Attempting to reverse-engineer or de-obfuscate data violates the platform terms of service.

## License

MIT: see [LICENSE](LICENSE).
