everestapi 0.2.0__tar.gz → 0.2.2__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {everestapi-0.2.0/src/everestapi.egg-info → everestapi-0.2.2}/PKG-INFO +6 -6
- {everestapi-0.2.0 → everestapi-0.2.2}/README.md +2 -2
- {everestapi-0.2.0 → everestapi-0.2.2}/pyproject.toml +3 -3
- {everestapi-0.2.0 → everestapi-0.2.2}/src/everestapi/__init__.py +1 -1
- {everestapi-0.2.0 → everestapi-0.2.2}/src/everestapi/cli.py +26 -19
- {everestapi-0.2.0 → everestapi-0.2.2}/src/everestapi/client.py +85 -44
- {everestapi-0.2.0 → everestapi-0.2.2}/src/everestapi/mcp/server.py +112 -36
- {everestapi-0.2.0 → everestapi-0.2.2/src/everestapi.egg-info}/PKG-INFO +6 -6
- {everestapi-0.2.0 → everestapi-0.2.2}/tests/test_cli.py +1 -0
- {everestapi-0.2.0 → everestapi-0.2.2}/tests/test_client.py +5 -2
- {everestapi-0.2.0 → everestapi-0.2.2}/tests/test_mcp_and_models.py +31 -11
- {everestapi-0.2.0 → everestapi-0.2.2}/LICENSE +0 -0
- {everestapi-0.2.0 → everestapi-0.2.2}/setup.cfg +0 -0
- {everestapi-0.2.0 → everestapi-0.2.2}/src/everestapi/__main__.py +0 -0
- {everestapi-0.2.0 → everestapi-0.2.2}/src/everestapi/mcp/__init__.py +0 -0
- {everestapi-0.2.0 → everestapi-0.2.2}/src/everestapi/mcp/__main__.py +0 -0
- {everestapi-0.2.0 → everestapi-0.2.2}/src/everestapi/types.py +0 -0
- {everestapi-0.2.0 → everestapi-0.2.2}/src/everestapi.egg-info/SOURCES.txt +0 -0
- {everestapi-0.2.0 → everestapi-0.2.2}/src/everestapi.egg-info/dependency_links.txt +0 -0
- {everestapi-0.2.0 → everestapi-0.2.2}/src/everestapi.egg-info/entry_points.txt +0 -0
- {everestapi-0.2.0 → everestapi-0.2.2}/src/everestapi.egg-info/requires.txt +0 -0
- {everestapi-0.2.0 → everestapi-0.2.2}/src/everestapi.egg-info/top_level.txt +0 -0
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: everestapi
|
|
3
|
-
Version: 0.2.
|
|
3
|
+
Version: 0.2.2
|
|
4
4
|
Summary: Python SDK for the EverestQuant prediction tournament platform
|
|
5
|
-
Author-email: EverestQuant <
|
|
5
|
+
Author-email: EverestQuant <support@everesteer.ai>
|
|
6
6
|
License-Expression: MIT
|
|
7
|
-
Project-URL: Homepage, https://
|
|
8
|
-
Project-URL: Documentation, https://docs.
|
|
7
|
+
Project-URL: Homepage, https://everesteer.ai
|
|
8
|
+
Project-URL: Documentation, https://docs.everesteer.ai
|
|
9
9
|
Project-URL: Repository, https://github.com/everestquant/everestapi-public
|
|
10
10
|
Keywords: quant,tournament,prediction,staking,machine-learning
|
|
11
11
|
Classifier: Development Status :: 4 - Beta
|
|
@@ -30,7 +30,7 @@ Dynamic: license-file
|
|
|
30
30
|
|
|
31
31
|
# everestapi
|
|
32
32
|
|
|
33
|
-
Python SDK for the [EverestQuant](https://
|
|
33
|
+
Python SDK for the [EverestQuant](https://everesteer.ai) prediction tournament platform.
|
|
34
34
|
|
|
35
35
|
```bash
|
|
36
36
|
pip install everestapi
|
|
@@ -59,7 +59,7 @@ api.submit_futures_predictions(
|
|
|
59
59
|
scores = api.get_scores(model_id="my-model", days=30)
|
|
60
60
|
```
|
|
61
61
|
|
|
62
|
-
Or set `
|
|
62
|
+
Or set `EIQ_API_KEY` as an environment variable and omit the constructor argument.
|
|
63
63
|
|
|
64
64
|
> **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.
|
|
65
65
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# everestapi
|
|
2
2
|
|
|
3
|
-
Python SDK for the [EverestQuant](https://
|
|
3
|
+
Python SDK for the [EverestQuant](https://everesteer.ai) prediction tournament platform.
|
|
4
4
|
|
|
5
5
|
```bash
|
|
6
6
|
pip install everestapi
|
|
@@ -29,7 +29,7 @@ api.submit_futures_predictions(
|
|
|
29
29
|
scores = api.get_scores(model_id="my-model", days=30)
|
|
30
30
|
```
|
|
31
31
|
|
|
32
|
-
Or set `
|
|
32
|
+
Or set `EIQ_API_KEY` as an environment variable and omit the constructor argument.
|
|
33
33
|
|
|
34
34
|
> **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.
|
|
35
35
|
|
|
@@ -9,7 +9,7 @@ description = "Python SDK for the EverestQuant prediction tournament platform"
|
|
|
9
9
|
readme = "README.md"
|
|
10
10
|
license = "MIT"
|
|
11
11
|
requires-python = ">=3.10"
|
|
12
|
-
authors = [{ name = "EverestQuant", email = "
|
|
12
|
+
authors = [{ name = "EverestQuant", email = "support@everesteer.ai" }]
|
|
13
13
|
keywords = ["quant", "tournament", "prediction", "staking", "machine-learning"]
|
|
14
14
|
classifiers = [
|
|
15
15
|
"Development Status :: 4 - Beta",
|
|
@@ -36,8 +36,8 @@ mcp = ["mcp>=1.0"]
|
|
|
36
36
|
everestapi = "everestapi.cli:cli"
|
|
37
37
|
|
|
38
38
|
[project.urls]
|
|
39
|
-
Homepage = "https://
|
|
40
|
-
Documentation = "https://docs.
|
|
39
|
+
Homepage = "https://everesteer.ai"
|
|
40
|
+
Documentation = "https://docs.everesteer.ai"
|
|
41
41
|
Repository = "https://github.com/everestquant/everestapi-public"
|
|
42
42
|
|
|
43
43
|
[tool.setuptools.packages.find]
|
|
@@ -36,7 +36,7 @@ def cli() -> None:
|
|
|
36
36
|
|
|
37
37
|
|
|
38
38
|
@cli.command()
|
|
39
|
-
@click.option("--api-key", envvar="EVEREST_API_KEY", default=None)
|
|
39
|
+
@click.option("--api-key", envvar=["EIQ_API_KEY", "EVEREST_API_KEY"], default=None)
|
|
40
40
|
def health(api_key: str | None) -> None:
|
|
41
41
|
"""Check API health."""
|
|
42
42
|
api = _get_api(api_key)
|
|
@@ -51,7 +51,7 @@ def health(api_key: str | None) -> None:
|
|
|
51
51
|
@click.option("--universe", "-u", default="futures", help="Universe (futures/equities)")
|
|
52
52
|
@click.option("--split", "-s", default="train", help="Split (train/validation/test/live)")
|
|
53
53
|
@click.option("--output", "-o", default=None, help="Output file path")
|
|
54
|
-
@click.option("--api-key", envvar="EVEREST_API_KEY", default=None)
|
|
54
|
+
@click.option("--api-key", envvar=["EIQ_API_KEY", "EVEREST_API_KEY"], default=None)
|
|
55
55
|
def download_data(universe: str, split: str, output: str | None, api_key: str | None) -> None:
|
|
56
56
|
"""Download tournament dataset."""
|
|
57
57
|
api = _get_api(api_key)
|
|
@@ -68,12 +68,16 @@ def download_data(universe: str, split: str, output: str | None, api_key: str |
|
|
|
68
68
|
@click.option("--split", "-s", default="validation")
|
|
69
69
|
@click.option("--version", "-v", default="v0")
|
|
70
70
|
@click.option("--output", "-o", default=None)
|
|
71
|
-
@click.option("--api-key", envvar="EVEREST_API_KEY", default=None)
|
|
72
|
-
def download_benchmark(
|
|
71
|
+
@click.option("--api-key", envvar=["EIQ_API_KEY", "EVEREST_API_KEY"], default=None)
|
|
72
|
+
def download_benchmark(
|
|
73
|
+
universe: str, split: str, version: str, output: str | None, api_key: str | None
|
|
74
|
+
) -> None:
|
|
73
75
|
"""Download benchmark model predictions."""
|
|
74
76
|
api = _get_api(api_key)
|
|
75
77
|
try:
|
|
76
|
-
path = api.download_benchmark(
|
|
78
|
+
path = api.download_benchmark(
|
|
79
|
+
universe=universe, split=split, version=version, output_path=output
|
|
80
|
+
)
|
|
77
81
|
click.echo(f"Downloaded to {path}")
|
|
78
82
|
except EverestError as e:
|
|
79
83
|
click.echo(f"Error: {e}", err=True)
|
|
@@ -83,12 +87,14 @@ def download_benchmark(universe: str, split: str, version: str, output: str | No
|
|
|
83
87
|
@cli.command()
|
|
84
88
|
@click.option("--model", "-m", required=True, help="Model identifier")
|
|
85
89
|
@click.option(
|
|
86
|
-
"--file",
|
|
90
|
+
"--file",
|
|
91
|
+
"-f",
|
|
92
|
+
"file_path",
|
|
87
93
|
required=True,
|
|
88
94
|
help="Predictions file (.parquet or .csv). Parquet recommended.",
|
|
89
95
|
)
|
|
90
96
|
@click.option("--tournament", "-t", default="equities", help="Tournament (equities/futures)")
|
|
91
|
-
@click.option("--api-key", envvar="EVEREST_API_KEY", default=None)
|
|
97
|
+
@click.option("--api-key", envvar=["EIQ_API_KEY", "EVEREST_API_KEY"], default=None)
|
|
92
98
|
def submit(model: str, file_path: str, tournament: str, api_key: str | None) -> None:
|
|
93
99
|
"""Submit predictions from a Parquet or CSV file.
|
|
94
100
|
|
|
@@ -102,7 +108,9 @@ def submit(model: str, file_path: str, tournament: str, api_key: str | None) ->
|
|
|
102
108
|
try:
|
|
103
109
|
if ext == "parquet":
|
|
104
110
|
result = api.submit_predictions_file(
|
|
105
|
-
model_name=model,
|
|
111
|
+
model_name=model,
|
|
112
|
+
file_path=file_path,
|
|
113
|
+
tournament=tournament,
|
|
106
114
|
)
|
|
107
115
|
elif ext == "csv":
|
|
108
116
|
import csv as _csv
|
|
@@ -115,8 +123,7 @@ def submit(model: str, file_path: str, tournament: str, api_key: str | None) ->
|
|
|
115
123
|
result = api.submit_futures_predictions(model_id=model, predictions=preds)
|
|
116
124
|
else:
|
|
117
125
|
preds_list = [
|
|
118
|
-
{"ticker": row["ticker"], "score": float(row["score"])}
|
|
119
|
-
for row in rows
|
|
126
|
+
{"ticker": row["ticker"], "score": float(row["score"])} for row in rows
|
|
120
127
|
]
|
|
121
128
|
result = api.submit_predictions(model_id=model, predictions=preds_list)
|
|
122
129
|
else:
|
|
@@ -135,7 +142,7 @@ def submit(model: str, file_path: str, tournament: str, api_key: str | None) ->
|
|
|
135
142
|
@cli.command()
|
|
136
143
|
@click.option("--period", "-p", default="30d", help="Period (7d/30d/90d/all)")
|
|
137
144
|
@click.option("--tournament", "-t", default="equities", help="Tournament (equities/futures)")
|
|
138
|
-
@click.option("--api-key", envvar="EVEREST_API_KEY", default=None)
|
|
145
|
+
@click.option("--api-key", envvar=["EIQ_API_KEY", "EVEREST_API_KEY"], default=None)
|
|
139
146
|
def leaderboard(period: str, tournament: str, api_key: str | None) -> None:
|
|
140
147
|
"""Show tournament leaderboard."""
|
|
141
148
|
api = _get_api(api_key)
|
|
@@ -153,7 +160,7 @@ def leaderboard(period: str, tournament: str, api_key: str | None) -> None:
|
|
|
153
160
|
@cli.command()
|
|
154
161
|
@click.option("--model", "-m", required=True, help="Model identifier")
|
|
155
162
|
@click.option("--days", "-d", default=30, help="Look-back window")
|
|
156
|
-
@click.option("--api-key", envvar="EVEREST_API_KEY", default=None)
|
|
163
|
+
@click.option("--api-key", envvar=["EIQ_API_KEY", "EVEREST_API_KEY"], default=None)
|
|
157
164
|
def scores(model: str, days: int, api_key: str | None) -> None:
|
|
158
165
|
"""Show scoring results for a model."""
|
|
159
166
|
api = _get_api(api_key)
|
|
@@ -166,7 +173,7 @@ def scores(model: str, days: int, api_key: str | None) -> None:
|
|
|
166
173
|
|
|
167
174
|
@cli.command()
|
|
168
175
|
@click.option("--model", "-m", required=True, help="Model identifier")
|
|
169
|
-
@click.option("--api-key", envvar="EVEREST_API_KEY", default=None)
|
|
176
|
+
@click.option("--api-key", envvar=["EIQ_API_KEY", "EVEREST_API_KEY"], default=None)
|
|
170
177
|
def diagnostics(model: str, api_key: str | None) -> None:
|
|
171
178
|
"""Show per-round diagnostics for a model."""
|
|
172
179
|
api = _get_api(api_key)
|
|
@@ -180,7 +187,7 @@ def diagnostics(model: str, api_key: str | None) -> None:
|
|
|
180
187
|
@cli.command()
|
|
181
188
|
@click.option("--tournament", "-t", default="equities", help="Tournament (equities/futures)")
|
|
182
189
|
@click.option("--limit", "-n", default=10, help="Number of rounds")
|
|
183
|
-
@click.option("--api-key", envvar="EVEREST_API_KEY", default=None)
|
|
190
|
+
@click.option("--api-key", envvar=["EIQ_API_KEY", "EVEREST_API_KEY"], default=None)
|
|
184
191
|
def rounds(tournament: str, limit: int, api_key: str | None) -> None:
|
|
185
192
|
"""List tournament rounds."""
|
|
186
193
|
api = _get_api(api_key)
|
|
@@ -192,7 +199,7 @@ def rounds(tournament: str, limit: int, api_key: str | None) -> None:
|
|
|
192
199
|
|
|
193
200
|
|
|
194
201
|
@cli.command()
|
|
195
|
-
@click.option("--api-key", envvar="EVEREST_API_KEY", default=None)
|
|
202
|
+
@click.option("--api-key", envvar=["EIQ_API_KEY", "EVEREST_API_KEY"], default=None)
|
|
196
203
|
def credits(api_key: str | None) -> None:
|
|
197
204
|
"""Check compute credit balance."""
|
|
198
205
|
api = _get_api(api_key)
|
|
@@ -205,7 +212,7 @@ def credits(api_key: str | None) -> None:
|
|
|
205
212
|
|
|
206
213
|
@cli.command()
|
|
207
214
|
@click.option("--model", "-m", required=True, help="Model identifier")
|
|
208
|
-
@click.option("--api-key", envvar="EVEREST_API_KEY", default=None)
|
|
215
|
+
@click.option("--api-key", envvar=["EIQ_API_KEY", "EVEREST_API_KEY"], default=None)
|
|
209
216
|
def stake_balance(model: str, api_key: str | None) -> None:
|
|
210
217
|
"""Show stake balance and pending payouts."""
|
|
211
218
|
api = _get_api(api_key)
|
|
@@ -227,15 +234,15 @@ def register(name: str, email: str) -> None:
|
|
|
227
234
|
click.echo("Registration successful!")
|
|
228
235
|
click.echo(f" Agent ID: {result['agent_id']}")
|
|
229
236
|
click.echo(f" API Key: {result['api_key']} (save this — shown once)")
|
|
230
|
-
click.echo(
|
|
231
|
-
click.echo(f" export
|
|
237
|
+
click.echo("\nSet your API key:")
|
|
238
|
+
click.echo(f" export EIQ_API_KEY={result['api_key']}")
|
|
232
239
|
except EverestError as e:
|
|
233
240
|
click.echo(f"Error: {e}", err=True)
|
|
234
241
|
sys.exit(1)
|
|
235
242
|
|
|
236
243
|
|
|
237
244
|
@cli.command()
|
|
238
|
-
@click.option("--api-key", envvar="EVEREST_API_KEY", default=None)
|
|
245
|
+
@click.option("--api-key", envvar=["EIQ_API_KEY", "EVEREST_API_KEY"], default=None)
|
|
239
246
|
def universe(api_key: str | None) -> None:
|
|
240
247
|
"""Show current tournament universe."""
|
|
241
248
|
api = _get_api(api_key)
|
|
@@ -86,6 +86,7 @@ def _open_no_symlink(path: str):
|
|
|
86
86
|
st = None
|
|
87
87
|
if st is not None:
|
|
88
88
|
import stat as _stat
|
|
89
|
+
|
|
89
90
|
if _stat.S_ISLNK(st.st_mode):
|
|
90
91
|
raise OSError(
|
|
91
92
|
f"Refusing to write to symlink at {path!r} (symlink-following blocked)",
|
|
@@ -108,9 +109,9 @@ class EverestAPI:
|
|
|
108
109
|
Parameters
|
|
109
110
|
----------
|
|
110
111
|
api_key : str, optional
|
|
111
|
-
EverestQuant API key. Falls back to ``EVEREST_API_KEY`` env var.
|
|
112
|
+
EverestQuant API key. Falls back to ``EIQ_API_KEY`` (or legacy ``EVEREST_API_KEY``) env var.
|
|
112
113
|
base_url : str, optional
|
|
113
|
-
Base URL for the API. Defaults to ``https://
|
|
114
|
+
Base URL for the API. Defaults to ``https://everesteer.ai`` (apex
|
|
114
115
|
domain), overridable via ``EVEREST_API_URL`` env var.
|
|
115
116
|
timeout : float
|
|
116
117
|
HTTP timeout in seconds.
|
|
@@ -124,7 +125,7 @@ class EverestAPI:
|
|
|
124
125
|
explicitly supplied.
|
|
125
126
|
"""
|
|
126
127
|
|
|
127
|
-
DEFAULT_BASE_URL = os.getenv("EVEREST_API_URL", "https://
|
|
128
|
+
DEFAULT_BASE_URL = os.getenv("EVEREST_API_URL", "https://everesteer.ai")
|
|
128
129
|
|
|
129
130
|
def __init__(
|
|
130
131
|
self,
|
|
@@ -136,7 +137,7 @@ class EverestAPI:
|
|
|
136
137
|
cf_access_client_id: str | None = None,
|
|
137
138
|
cf_access_client_secret: str | None = None,
|
|
138
139
|
) -> None:
|
|
139
|
-
self.api_key = api_key or os.getenv("EVEREST_API_KEY", "")
|
|
140
|
+
self.api_key = api_key or os.getenv("EIQ_API_KEY") or os.getenv("EVEREST_API_KEY", "")
|
|
140
141
|
self.base_url = (base_url or self.DEFAULT_BASE_URL).rstrip("/")
|
|
141
142
|
self.tournament = tournament
|
|
142
143
|
|
|
@@ -307,7 +308,9 @@ class EverestAPI:
|
|
|
307
308
|
return self.get_validation_panel(model_id=model_id, days=days)
|
|
308
309
|
|
|
309
310
|
def get_model_per_exped_breakdown(
|
|
310
|
-
self,
|
|
311
|
+
self,
|
|
312
|
+
model_id: str,
|
|
313
|
+
days: int = 3650,
|
|
311
314
|
) -> list[float]:
|
|
312
315
|
"""Return the per-exped CORR series for a model (oldest→newest).
|
|
313
316
|
|
|
@@ -317,7 +320,10 @@ class EverestAPI:
|
|
|
317
320
|
return list(resp.get("per_exped_corr", []))
|
|
318
321
|
|
|
319
322
|
def get_job_output(
|
|
320
|
-
self,
|
|
323
|
+
self,
|
|
324
|
+
job_id: str,
|
|
325
|
+
filename: str,
|
|
326
|
+
output_path: str | None = None,
|
|
321
327
|
) -> str:
|
|
322
328
|
"""Download an output file from a completed compute job.
|
|
323
329
|
|
|
@@ -332,7 +338,8 @@ class EverestAPI:
|
|
|
332
338
|
# like "../etc/passwd" can't escape cwd.
|
|
333
339
|
output_path = _safe_output_path(filename, server_supplied=True)
|
|
334
340
|
return self._download(
|
|
335
|
-
f"/api/v1/compute/jobs/{job_id}/output/{filename}",
|
|
341
|
+
f"/api/v1/compute/jobs/{job_id}/output/{filename}",
|
|
342
|
+
output_path,
|
|
336
343
|
)
|
|
337
344
|
|
|
338
345
|
def get_scores(self, model_id: str, days: int = 30) -> dict:
|
|
@@ -443,8 +450,7 @@ class EverestAPI:
|
|
|
443
450
|
"model_id": model_id,
|
|
444
451
|
"exped": exped or "current",
|
|
445
452
|
"predictions": [
|
|
446
|
-
{"instrument_id": iid, "prediction": pred}
|
|
447
|
-
for iid, pred in predictions.items()
|
|
453
|
+
{"instrument_id": iid, "prediction": pred} for iid, pred in predictions.items()
|
|
448
454
|
],
|
|
449
455
|
}
|
|
450
456
|
return self._request("POST", "/api/v1/futures/submit/v2", json=body)
|
|
@@ -460,10 +466,7 @@ class EverestAPI:
|
|
|
460
466
|
.. deprecated::
|
|
461
467
|
Use :meth:`submit_futures_predictions` (v2) instead.
|
|
462
468
|
"""
|
|
463
|
-
preds_list = [
|
|
464
|
-
{"instrument_id": k, "targets": v}
|
|
465
|
-
for k, v in sorted(predictions.items())
|
|
466
|
-
]
|
|
469
|
+
preds_list = [{"instrument_id": k, "targets": v} for k, v in sorted(predictions.items())]
|
|
467
470
|
body = {"model_id": model_id, "exped": exped, "predictions": preds_list}
|
|
468
471
|
return self._request("POST", "/api/v1/futures/submit", json=body)
|
|
469
472
|
|
|
@@ -511,7 +514,8 @@ class EverestAPI:
|
|
|
511
514
|
if version in (None, "latest", "current"):
|
|
512
515
|
version = self._current_dataset_version(universe) or "v0"
|
|
513
516
|
return self._download(
|
|
514
|
-
f"/api/v1/data/{version}/benchmark/{universe}/{split}",
|
|
517
|
+
f"/api/v1/data/{version}/benchmark/{universe}/{split}",
|
|
518
|
+
output_path,
|
|
515
519
|
)
|
|
516
520
|
|
|
517
521
|
def download_ai_model(
|
|
@@ -524,7 +528,8 @@ class EverestAPI:
|
|
|
524
528
|
if output_path is None:
|
|
525
529
|
output_path = f"ai_model_{universe}.parquet"
|
|
526
530
|
return self._download(
|
|
527
|
-
f"/api/v1/data/{version}/ai-model/{universe}",
|
|
531
|
+
f"/api/v1/data/{version}/ai-model/{universe}",
|
|
532
|
+
output_path,
|
|
528
533
|
)
|
|
529
534
|
|
|
530
535
|
# -- compute ----------------------------------------------------------
|
|
@@ -539,7 +544,9 @@ class EverestAPI:
|
|
|
539
544
|
) -> dict:
|
|
540
545
|
"""Submit Tier 1 serverless training job."""
|
|
541
546
|
body: dict = {
|
|
542
|
-
"model": model,
|
|
547
|
+
"model": model,
|
|
548
|
+
"features": features,
|
|
549
|
+
"target": target,
|
|
543
550
|
"universe": universe,
|
|
544
551
|
}
|
|
545
552
|
if params:
|
|
@@ -599,6 +606,7 @@ class EverestAPI:
|
|
|
599
606
|
def wait_for_job(self, job_id: str, timeout: int = 3600) -> dict:
|
|
600
607
|
"""Block until job completes. Exponential backoff polling."""
|
|
601
608
|
import time
|
|
609
|
+
|
|
602
610
|
delay = 2.0
|
|
603
611
|
elapsed = 0.0
|
|
604
612
|
while elapsed < timeout:
|
|
@@ -676,15 +684,24 @@ class EverestAPI:
|
|
|
676
684
|
|
|
677
685
|
def get_rounds(self, tournament: str = "equities", limit: int = 25) -> dict:
|
|
678
686
|
"""GET /api/v1/rounds — list tournament rounds."""
|
|
679
|
-
return self._request(
|
|
680
|
-
"
|
|
681
|
-
|
|
687
|
+
return self._request(
|
|
688
|
+
"GET",
|
|
689
|
+
"/api/v1/rounds",
|
|
690
|
+
params={
|
|
691
|
+
"tournament": tournament,
|
|
692
|
+
"limit": str(limit),
|
|
693
|
+
},
|
|
694
|
+
)
|
|
682
695
|
|
|
683
696
|
def get_current_round(self, tournament: str = "equities") -> dict:
|
|
684
697
|
"""GET /api/v1/rounds/current — current active round."""
|
|
685
|
-
return self._request(
|
|
686
|
-
"
|
|
687
|
-
|
|
698
|
+
return self._request(
|
|
699
|
+
"GET",
|
|
700
|
+
"/api/v1/rounds/current",
|
|
701
|
+
params={
|
|
702
|
+
"tournament": tournament,
|
|
703
|
+
},
|
|
704
|
+
)
|
|
688
705
|
|
|
689
706
|
def get_schedule(self) -> dict:
|
|
690
707
|
"""GET /api/v1/schedule — round schedule for both tournaments."""
|
|
@@ -715,9 +732,14 @@ class EverestAPI:
|
|
|
715
732
|
|
|
716
733
|
def set_multipliers(self, model_id: str, corr_mult: float, aimc_mult: float) -> dict:
|
|
717
734
|
"""PATCH /api/v1/models/{model_id}/multipliers — set payout multipliers."""
|
|
718
|
-
return self._request(
|
|
719
|
-
"
|
|
720
|
-
|
|
735
|
+
return self._request(
|
|
736
|
+
"PATCH",
|
|
737
|
+
f"/api/v1/models/{model_id}/multipliers",
|
|
738
|
+
json={
|
|
739
|
+
"corr_multiplier": corr_mult,
|
|
740
|
+
"aimc_multiplier": aimc_mult,
|
|
741
|
+
},
|
|
742
|
+
)
|
|
721
743
|
|
|
722
744
|
def get_multipliers(self, model_id: str) -> dict:
|
|
723
745
|
"""GET /api/v1/models/{model_id}/multipliers"""
|
|
@@ -741,17 +763,26 @@ class EverestAPI:
|
|
|
741
763
|
|
|
742
764
|
def stake(self, model_id: str, amount_usdc: float, wallet_address: str) -> dict:
|
|
743
765
|
"""Stake USDC on a model."""
|
|
744
|
-
return self._request(
|
|
745
|
-
"
|
|
746
|
-
"
|
|
747
|
-
|
|
748
|
-
|
|
766
|
+
return self._request(
|
|
767
|
+
"POST",
|
|
768
|
+
"/api/v1/staking/stake",
|
|
769
|
+
json={
|
|
770
|
+
"model_id": model_id,
|
|
771
|
+
"amount_usdc": amount_usdc,
|
|
772
|
+
"wallet_address": wallet_address,
|
|
773
|
+
},
|
|
774
|
+
)
|
|
749
775
|
|
|
750
776
|
def confirm_stake(self, stake_id: str, txn_hash: str) -> dict:
|
|
751
777
|
"""Confirm a stake with on-chain transaction hash."""
|
|
752
|
-
return self._request(
|
|
753
|
-
"
|
|
754
|
-
|
|
778
|
+
return self._request(
|
|
779
|
+
"POST",
|
|
780
|
+
"/api/v1/staking/confirm",
|
|
781
|
+
json={
|
|
782
|
+
"stake_id": stake_id,
|
|
783
|
+
"txn_hash": txn_hash,
|
|
784
|
+
},
|
|
785
|
+
)
|
|
755
786
|
|
|
756
787
|
def unstake(self, stake_id: str) -> dict:
|
|
757
788
|
"""Unstake USDC from a model."""
|
|
@@ -763,9 +794,14 @@ class EverestAPI:
|
|
|
763
794
|
|
|
764
795
|
def claim_payout(self, model_id: str, round_id: str) -> dict:
|
|
765
796
|
"""Claim resolved payout for a model and round."""
|
|
766
|
-
return self._request(
|
|
767
|
-
"
|
|
768
|
-
|
|
797
|
+
return self._request(
|
|
798
|
+
"POST",
|
|
799
|
+
"/api/v1/staking/claim",
|
|
800
|
+
json={
|
|
801
|
+
"model_id": model_id,
|
|
802
|
+
"round_id": round_id,
|
|
803
|
+
},
|
|
804
|
+
)
|
|
769
805
|
|
|
770
806
|
def get_staking_history(self, model_id: str) -> dict:
|
|
771
807
|
"""Get stake/unstake/claim history."""
|
|
@@ -787,9 +823,14 @@ class EverestAPI:
|
|
|
787
823
|
|
|
788
824
|
def withdraw_usdc(self, to_address: str, amount_usdc: float) -> dict:
|
|
789
825
|
"""Withdraw USDC to external wallet."""
|
|
790
|
-
return self._request(
|
|
791
|
-
"
|
|
792
|
-
|
|
826
|
+
return self._request(
|
|
827
|
+
"POST",
|
|
828
|
+
"/api/v1/staking/withdraw",
|
|
829
|
+
json={
|
|
830
|
+
"to_address": to_address,
|
|
831
|
+
"amount_usdc": amount_usdc,
|
|
832
|
+
},
|
|
833
|
+
)
|
|
793
834
|
|
|
794
835
|
def get_forwarder_balance(self) -> dict:
|
|
795
836
|
"""GET /api/v1/staking/forwarder-balance"""
|
|
@@ -836,13 +877,13 @@ class EverestAPI:
|
|
|
836
877
|
|
|
837
878
|
@staticmethod
|
|
838
879
|
def evaluate(
|
|
839
|
-
predictions:
|
|
840
|
-
validation_data:
|
|
880
|
+
predictions: np.ndarray | pd.Series,
|
|
881
|
+
validation_data: pd.DataFrame,
|
|
841
882
|
target: str = "target_everest_20",
|
|
842
883
|
) -> dict:
|
|
843
884
|
"""Evaluate predictions against validation set locally."""
|
|
844
885
|
import numpy as np
|
|
845
|
-
from scipy.stats import
|
|
886
|
+
from scipy.stats import rankdata, spearmanr
|
|
846
887
|
|
|
847
888
|
preds = np.asarray(predictions)
|
|
848
889
|
targets = validation_data[target].values
|
|
@@ -864,8 +905,8 @@ class EverestAPI:
|
|
|
864
905
|
pd_dev = pred_ranks - pm
|
|
865
906
|
td_dev = target_ranks - tm
|
|
866
907
|
cov = np.dot(weights, pd_dev * td_dev) / w_sum
|
|
867
|
-
pvar = np.dot(weights, pd_dev
|
|
868
|
-
tvar = np.dot(weights, td_dev
|
|
908
|
+
pvar = np.dot(weights, pd_dev**2) / w_sum
|
|
909
|
+
tvar = np.dot(weights, td_dev**2) / w_sum
|
|
869
910
|
denom = np.sqrt(pvar * tvar)
|
|
870
911
|
result["weighted_corr"] = round(float(cov / denom), 6) if denom > 0 else 0.0
|
|
871
912
|
else:
|
|
@@ -10,8 +10,8 @@ Run::
|
|
|
10
10
|
python -m everestapi.mcp
|
|
11
11
|
|
|
12
12
|
Requires environment variables:
|
|
13
|
-
|
|
14
|
-
EIQ_BASE_URL — API base URL (default: https://
|
|
13
|
+
EIQ_API_KEY — your EverestQuant API key (also accepts legacy EVEREST_API_KEY)
|
|
14
|
+
EIQ_BASE_URL — API base URL (default: https://everesteer.ai)
|
|
15
15
|
CF_ACCESS_CLIENT_ID / CF_ACCESS_CLIENT_SECRET — Cloudflare Access service
|
|
16
16
|
token, required only when the host sits behind CF Access (e.g. staging)
|
|
17
17
|
"""
|
|
@@ -47,7 +47,7 @@ _client = None
|
|
|
47
47
|
def _get_client():
|
|
48
48
|
"""Lazily build (and cache) the EverestAPI SDK client.
|
|
49
49
|
|
|
50
|
-
Reads ``
|
|
50
|
+
Reads ``EIQ_API_KEY`` (or legacy ``EVEREST_API_KEY``) and ``EIQ_BASE_URL`` (or
|
|
51
51
|
``EVEREST_API_URL``) from the environment. Cloudflare Access service tokens
|
|
52
52
|
(``CF_ACCESS_CLIENT_ID`` / ``CF_ACCESS_CLIENT_SECRET``) are honoured by the
|
|
53
53
|
EverestAPI constructor, so a host behind CF Access (e.g. staging) works
|
|
@@ -57,14 +57,14 @@ def _get_client():
|
|
|
57
57
|
if _client is None:
|
|
58
58
|
from everestapi import EverestAPI
|
|
59
59
|
|
|
60
|
-
api_key = os.environ.get("
|
|
60
|
+
api_key = os.environ.get("EIQ_API_KEY") or os.environ.get("EVEREST_API_KEY", "")
|
|
61
61
|
base_url = (
|
|
62
62
|
os.environ.get("EIQ_BASE_URL")
|
|
63
63
|
or os.environ.get("EVEREST_API_URL")
|
|
64
|
-
or "https://
|
|
64
|
+
or "https://everesteer.ai"
|
|
65
65
|
)
|
|
66
66
|
if not api_key:
|
|
67
|
-
raise RuntimeError("
|
|
67
|
+
raise RuntimeError("EIQ_API_KEY environment variable is required")
|
|
68
68
|
# EverestAPI reads CF_ACCESS_CLIENT_ID/SECRET from the environment in its
|
|
69
69
|
# own constructor, so staging (behind Cloudflare Access) works with no
|
|
70
70
|
# extra wiring here.
|
|
@@ -189,7 +189,11 @@ TOOLS = [
|
|
|
189
189
|
"inputSchema": {
|
|
190
190
|
"type": "object",
|
|
191
191
|
"properties": {
|
|
192
|
-
"version": {
|
|
192
|
+
"version": {
|
|
193
|
+
"type": "string",
|
|
194
|
+
"description": "Dataset version (default: bregen)",
|
|
195
|
+
"default": "bregen",
|
|
196
|
+
},
|
|
193
197
|
},
|
|
194
198
|
"required": [],
|
|
195
199
|
},
|
|
@@ -201,7 +205,10 @@ TOOLS = [
|
|
|
201
205
|
"type": "object",
|
|
202
206
|
"properties": {
|
|
203
207
|
"universe": {"type": "string", "description": "Universe: 'futures' or 'equities'"},
|
|
204
|
-
"split": {
|
|
208
|
+
"split": {
|
|
209
|
+
"type": "string",
|
|
210
|
+
"description": "Split: 'train', 'validation', or 'live'",
|
|
211
|
+
},
|
|
205
212
|
"output_path": {"type": "string", "description": "Local path to save (optional)"},
|
|
206
213
|
},
|
|
207
214
|
"required": ["universe", "split"],
|
|
@@ -214,7 +221,11 @@ TOOLS = [
|
|
|
214
221
|
"type": "object",
|
|
215
222
|
"properties": {
|
|
216
223
|
"universe": {"type": "string", "default": "futures"},
|
|
217
|
-
"split": {
|
|
224
|
+
"split": {
|
|
225
|
+
"type": "string",
|
|
226
|
+
"default": "validation",
|
|
227
|
+
"description": "'validation' or 'live'",
|
|
228
|
+
},
|
|
218
229
|
"output_path": {"type": "string", "description": "Local path (optional)"},
|
|
219
230
|
},
|
|
220
231
|
"required": [],
|
|
@@ -227,8 +238,16 @@ TOOLS = [
|
|
|
227
238
|
"type": "object",
|
|
228
239
|
"properties": {
|
|
229
240
|
"model": {"type": "string", "enum": ["lightgbm", "xgboost", "ridge", "mlp"]},
|
|
230
|
-
"features": {
|
|
231
|
-
|
|
241
|
+
"features": {
|
|
242
|
+
"type": "string",
|
|
243
|
+
"enum": ["small", "medium", "all"],
|
|
244
|
+
"default": "small",
|
|
245
|
+
},
|
|
246
|
+
"target": {
|
|
247
|
+
"type": "string",
|
|
248
|
+
"default": "target_everest_20",
|
|
249
|
+
"description": "Target column. Futures payout uses target_everest_20 (20-day forward return). Multiple target types available. See get_dataset_schema for full list.",
|
|
250
|
+
},
|
|
232
251
|
"universe": {"type": "string", "default": "futures"},
|
|
233
252
|
"params": {"type": "object", "description": "Model hyperparameters (optional)"},
|
|
234
253
|
},
|
|
@@ -255,7 +274,10 @@ TOOLS = [
|
|
|
255
274
|
"inputSchema": {
|
|
256
275
|
"type": "object",
|
|
257
276
|
"properties": {
|
|
258
|
-
"job_id": {
|
|
277
|
+
"job_id": {
|
|
278
|
+
"type": "string",
|
|
279
|
+
"description": "Job ID from quick_train or custom_train",
|
|
280
|
+
},
|
|
259
281
|
},
|
|
260
282
|
"required": ["job_id"],
|
|
261
283
|
},
|
|
@@ -266,7 +288,10 @@ TOOLS = [
|
|
|
266
288
|
"inputSchema": {
|
|
267
289
|
"type": "object",
|
|
268
290
|
"properties": {
|
|
269
|
-
"job_id": {
|
|
291
|
+
"job_id": {
|
|
292
|
+
"type": "string",
|
|
293
|
+
"description": "Job ID from quick_train or custom_train",
|
|
294
|
+
},
|
|
270
295
|
},
|
|
271
296
|
"required": ["job_id"],
|
|
272
297
|
},
|
|
@@ -286,8 +311,16 @@ TOOLS = [
|
|
|
286
311
|
"inputSchema": {
|
|
287
312
|
"type": "object",
|
|
288
313
|
"properties": {
|
|
289
|
-
"tournament": {
|
|
290
|
-
|
|
314
|
+
"tournament": {
|
|
315
|
+
"type": "string",
|
|
316
|
+
"description": "Tournament: 'equities' or 'futures'",
|
|
317
|
+
"default": "equities",
|
|
318
|
+
},
|
|
319
|
+
"limit": {
|
|
320
|
+
"type": "integer",
|
|
321
|
+
"description": "Max rounds to return (default 25)",
|
|
322
|
+
"default": 25,
|
|
323
|
+
},
|
|
291
324
|
},
|
|
292
325
|
"required": [],
|
|
293
326
|
},
|
|
@@ -298,7 +331,11 @@ TOOLS = [
|
|
|
298
331
|
"inputSchema": {
|
|
299
332
|
"type": "object",
|
|
300
333
|
"properties": {
|
|
301
|
-
"tournament": {
|
|
334
|
+
"tournament": {
|
|
335
|
+
"type": "string",
|
|
336
|
+
"description": "Tournament: 'equities' or 'futures'",
|
|
337
|
+
"default": "equities",
|
|
338
|
+
},
|
|
302
339
|
},
|
|
303
340
|
"required": [],
|
|
304
341
|
},
|
|
@@ -341,8 +378,14 @@ TOOLS = [
|
|
|
341
378
|
"inputSchema": {
|
|
342
379
|
"type": "object",
|
|
343
380
|
"properties": {
|
|
344
|
-
"name": {
|
|
345
|
-
|
|
381
|
+
"name": {
|
|
382
|
+
"type": "string",
|
|
383
|
+
"description": "Model name (this becomes the model_id for submit/upload).",
|
|
384
|
+
},
|
|
385
|
+
"description": {
|
|
386
|
+
"type": "string",
|
|
387
|
+
"description": "Optional human-readable description.",
|
|
388
|
+
},
|
|
346
389
|
},
|
|
347
390
|
"required": ["name"],
|
|
348
391
|
},
|
|
@@ -383,7 +426,11 @@ TOOLS = [
|
|
|
383
426
|
"type": "object",
|
|
384
427
|
"properties": {
|
|
385
428
|
"model_id": {"type": "string", "description": "Model to run diagnostics on"},
|
|
386
|
-
"days": {
|
|
429
|
+
"days": {
|
|
430
|
+
"type": "integer",
|
|
431
|
+
"description": "Number of days of history (default 365)",
|
|
432
|
+
"default": 365,
|
|
433
|
+
},
|
|
387
434
|
},
|
|
388
435
|
"required": ["model_id"],
|
|
389
436
|
},
|
|
@@ -413,7 +460,11 @@ TOOLS = [
|
|
|
413
460
|
"type": "object",
|
|
414
461
|
"properties": {
|
|
415
462
|
"model_id": {"type": "string", "description": "Model to query"},
|
|
416
|
-
"days": {
|
|
463
|
+
"days": {
|
|
464
|
+
"type": "integer",
|
|
465
|
+
"description": "History window in days (default 3650 = full tournament history)",
|
|
466
|
+
"default": 3650,
|
|
467
|
+
},
|
|
417
468
|
},
|
|
418
469
|
"required": ["model_id"],
|
|
419
470
|
},
|
|
@@ -426,7 +477,10 @@ TOOLS = [
|
|
|
426
477
|
"properties": {
|
|
427
478
|
"model_id": {"type": "string", "description": "Model to stake on."},
|
|
428
479
|
"amount_usdc": {"type": "number", "description": "Amount of USDC to stake."},
|
|
429
|
-
"wallet_address": {
|
|
480
|
+
"wallet_address": {
|
|
481
|
+
"type": "string",
|
|
482
|
+
"description": "On-chain wallet address holding the USDC.",
|
|
483
|
+
},
|
|
430
484
|
},
|
|
431
485
|
"required": ["model_id", "amount_usdc", "wallet_address"],
|
|
432
486
|
},
|
|
@@ -437,7 +491,10 @@ TOOLS = [
|
|
|
437
491
|
"inputSchema": {
|
|
438
492
|
"type": "object",
|
|
439
493
|
"properties": {
|
|
440
|
-
"stake_id": {
|
|
494
|
+
"stake_id": {
|
|
495
|
+
"type": "string",
|
|
496
|
+
"description": "Stake ID returned by stake_on_model.",
|
|
497
|
+
},
|
|
441
498
|
},
|
|
442
499
|
"required": ["stake_id"],
|
|
443
500
|
},
|
|
@@ -460,7 +517,10 @@ TOOLS = [
|
|
|
460
517
|
"type": "object",
|
|
461
518
|
"properties": {
|
|
462
519
|
"model_id": {"type": "string", "description": "Model identifier."},
|
|
463
|
-
"round_id": {
|
|
520
|
+
"round_id": {
|
|
521
|
+
"type": "string",
|
|
522
|
+
"description": "Round identifier for the payout to claim.",
|
|
523
|
+
},
|
|
464
524
|
},
|
|
465
525
|
"required": ["model_id", "round_id"],
|
|
466
526
|
},
|
|
@@ -503,7 +563,10 @@ TOOLS = [
|
|
|
503
563
|
"inputSchema": {
|
|
504
564
|
"type": "object",
|
|
505
565
|
"properties": {
|
|
506
|
-
"round_id": {
|
|
566
|
+
"round_id": {
|
|
567
|
+
"type": "integer",
|
|
568
|
+
"description": "Round identifier for the payout to claim.",
|
|
569
|
+
},
|
|
507
570
|
},
|
|
508
571
|
"required": ["round_id"],
|
|
509
572
|
},
|
|
@@ -729,6 +792,7 @@ def _dispatch(name: str, arguments: dict) -> str:
|
|
|
729
792
|
# MCP-SDK path (preferred)
|
|
730
793
|
# ===================================================================
|
|
731
794
|
|
|
795
|
+
|
|
732
796
|
def _build_mcp_server() -> Server:
|
|
733
797
|
"""Build and return a configured MCP Server instance."""
|
|
734
798
|
server = Server("eiq-mcp")
|
|
@@ -769,6 +833,7 @@ async def _run_mcp() -> None:
|
|
|
769
833
|
# Fallback: minimal JSON-RPC 2.0 over stdin/stdout
|
|
770
834
|
# ===================================================================
|
|
771
835
|
|
|
836
|
+
|
|
772
837
|
def _run_jsonrpc_fallback() -> None:
|
|
773
838
|
"""Minimal JSON-RPC 2.0 loop for environments without the ``mcp`` package."""
|
|
774
839
|
import sys
|
|
@@ -790,11 +855,14 @@ def _run_jsonrpc_fallback() -> None:
|
|
|
790
855
|
params = req.get("params", {})
|
|
791
856
|
|
|
792
857
|
if method == "initialize":
|
|
793
|
-
_jsonrpc_respond(
|
|
794
|
-
|
|
795
|
-
|
|
796
|
-
|
|
797
|
-
|
|
858
|
+
_jsonrpc_respond(
|
|
859
|
+
req_id,
|
|
860
|
+
result={
|
|
861
|
+
"protocolVersion": "2024-11-05",
|
|
862
|
+
"capabilities": {"tools": {"listChanged": False}},
|
|
863
|
+
"serverInfo": {"name": "eiq-mcp", "version": "0.1.0"},
|
|
864
|
+
},
|
|
865
|
+
)
|
|
798
866
|
elif method == "tools/list":
|
|
799
867
|
_jsonrpc_respond(req_id, result={"tools": TOOLS})
|
|
800
868
|
elif method == "tools/call":
|
|
@@ -802,14 +870,20 @@ def _run_jsonrpc_fallback() -> None:
|
|
|
802
870
|
arguments = params.get("arguments", {})
|
|
803
871
|
try:
|
|
804
872
|
text = _dispatch(tool_name, arguments)
|
|
805
|
-
_jsonrpc_respond(
|
|
806
|
-
|
|
807
|
-
|
|
873
|
+
_jsonrpc_respond(
|
|
874
|
+
req_id,
|
|
875
|
+
result={
|
|
876
|
+
"content": [{"type": "text", "text": text}],
|
|
877
|
+
},
|
|
878
|
+
)
|
|
808
879
|
except Exception as exc:
|
|
809
|
-
_jsonrpc_respond(
|
|
810
|
-
|
|
811
|
-
|
|
812
|
-
|
|
880
|
+
_jsonrpc_respond(
|
|
881
|
+
req_id,
|
|
882
|
+
result={
|
|
883
|
+
"content": [{"type": "text", "text": json.dumps({"error": str(exc)})}],
|
|
884
|
+
"isError": True,
|
|
885
|
+
},
|
|
886
|
+
)
|
|
813
887
|
elif method == "notifications/initialized":
|
|
814
888
|
pass # no response needed for notifications
|
|
815
889
|
else:
|
|
@@ -831,10 +905,12 @@ def _jsonrpc_respond(req_id, *, result=None, error=None) -> None:
|
|
|
831
905
|
# Entry point
|
|
832
906
|
# ===================================================================
|
|
833
907
|
|
|
908
|
+
|
|
834
909
|
def main() -> None:
|
|
835
910
|
"""Run the MCP server over stdio (the transport Claude Code / Cursor use)."""
|
|
836
911
|
if _HAS_MCP:
|
|
837
912
|
import asyncio
|
|
913
|
+
|
|
838
914
|
asyncio.run(_run_mcp())
|
|
839
915
|
else:
|
|
840
916
|
_run_jsonrpc_fallback()
|
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: everestapi
|
|
3
|
-
Version: 0.2.
|
|
3
|
+
Version: 0.2.2
|
|
4
4
|
Summary: Python SDK for the EverestQuant prediction tournament platform
|
|
5
|
-
Author-email: EverestQuant <
|
|
5
|
+
Author-email: EverestQuant <support@everesteer.ai>
|
|
6
6
|
License-Expression: MIT
|
|
7
|
-
Project-URL: Homepage, https://
|
|
8
|
-
Project-URL: Documentation, https://docs.
|
|
7
|
+
Project-URL: Homepage, https://everesteer.ai
|
|
8
|
+
Project-URL: Documentation, https://docs.everesteer.ai
|
|
9
9
|
Project-URL: Repository, https://github.com/everestquant/everestapi-public
|
|
10
10
|
Keywords: quant,tournament,prediction,staking,machine-learning
|
|
11
11
|
Classifier: Development Status :: 4 - Beta
|
|
@@ -30,7 +30,7 @@ Dynamic: license-file
|
|
|
30
30
|
|
|
31
31
|
# everestapi
|
|
32
32
|
|
|
33
|
-
Python SDK for the [EverestQuant](https://
|
|
33
|
+
Python SDK for the [EverestQuant](https://everesteer.ai) prediction tournament platform.
|
|
34
34
|
|
|
35
35
|
```bash
|
|
36
36
|
pip install everestapi
|
|
@@ -59,7 +59,7 @@ api.submit_futures_predictions(
|
|
|
59
59
|
scores = api.get_scores(model_id="my-model", days=30)
|
|
60
60
|
```
|
|
61
61
|
|
|
62
|
-
Or set `
|
|
62
|
+
Or set `EIQ_API_KEY` as an environment variable and omit the constructor argument.
|
|
63
63
|
|
|
64
64
|
> **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.
|
|
65
65
|
|
|
@@ -17,6 +17,7 @@ from everestapi.cli import cli
|
|
|
17
17
|
def runner(monkeypatch):
|
|
18
18
|
monkeypatch.setattr("everestapi.client.EverestAPI.DEFAULT_BASE_URL", "http://test")
|
|
19
19
|
monkeypatch.setenv("EVEREST_API_KEY", "eiq_test_key")
|
|
20
|
+
monkeypatch.delenv("EIQ_API_KEY", raising=False)
|
|
20
21
|
monkeypatch.delenv("EIQ_BASIC_AUTH_USER", raising=False)
|
|
21
22
|
monkeypatch.delenv("EIQ_BASIC_AUTH_PASS", raising=False)
|
|
22
23
|
return CliRunner()
|
|
@@ -16,6 +16,7 @@ from everestapi import EverestAPI, EverestError
|
|
|
16
16
|
def api(monkeypatch):
|
|
17
17
|
for var in (
|
|
18
18
|
"EVEREST_API_URL",
|
|
19
|
+
"EIQ_API_KEY",
|
|
19
20
|
"EVEREST_API_KEY",
|
|
20
21
|
"EIQ_BASIC_AUTH_USER",
|
|
21
22
|
"EIQ_BASIC_AUTH_PASS",
|
|
@@ -31,6 +32,7 @@ def test_api_key_header_set(httpx_mock, api):
|
|
|
31
32
|
|
|
32
33
|
|
|
33
34
|
def test_empty_api_key_omits_header(httpx_mock, monkeypatch):
|
|
35
|
+
monkeypatch.delenv("EIQ_API_KEY", raising=False)
|
|
34
36
|
monkeypatch.delenv("EVEREST_API_KEY", raising=False)
|
|
35
37
|
client = EverestAPI(base_url="http://test")
|
|
36
38
|
httpx_mock.add_response(url="http://test/api/v1/health", json={"status": "ok"})
|
|
@@ -122,8 +124,9 @@ def test_cf_service_token_kwargs_override_env(monkeypatch):
|
|
|
122
124
|
monkeypatch.setenv("CF_ACCESS_CLIENT_ID", "env.access")
|
|
123
125
|
monkeypatch.setenv("CF_ACCESS_CLIENT_SECRET", "envv")
|
|
124
126
|
kid, ksec = "kw.access", "kwv"
|
|
125
|
-
client = EverestAPI(
|
|
126
|
-
|
|
127
|
+
client = EverestAPI(
|
|
128
|
+
api_key=_K, base_url=_URL, cf_access_client_id=kid, cf_access_client_secret=ksec
|
|
129
|
+
)
|
|
127
130
|
h = client._client.headers
|
|
128
131
|
assert h["cf-access-client-id"] == kid
|
|
129
132
|
assert h["cf-access-client-secret"] == ksec
|
|
@@ -14,6 +14,7 @@ from everestapi import EverestAPI, EverestError
|
|
|
14
14
|
def api(monkeypatch):
|
|
15
15
|
for var in (
|
|
16
16
|
"EVEREST_API_URL",
|
|
17
|
+
"EIQ_API_KEY",
|
|
17
18
|
"EVEREST_API_KEY",
|
|
18
19
|
"CF_ACCESS_CLIENT_ID",
|
|
19
20
|
"CF_ACCESS_CLIENT_SECRET",
|
|
@@ -26,30 +27,37 @@ def api(monkeypatch):
|
|
|
26
27
|
|
|
27
28
|
# -- client: model management --------------------------------------------
|
|
28
29
|
|
|
30
|
+
|
|
29
31
|
def test_create_model_posts_body(httpx_mock, api):
|
|
30
32
|
httpx_mock.add_response(
|
|
31
|
-
url="http://test/api/v1/agents/me/models",
|
|
33
|
+
url="http://test/api/v1/agents/me/models",
|
|
34
|
+
method="POST",
|
|
32
35
|
json={"id": "m1", "name": "my-model"},
|
|
33
36
|
)
|
|
34
37
|
out = api.create_model("my-model", description="d")
|
|
35
38
|
assert out["name"] == "my-model"
|
|
36
39
|
assert json.loads(httpx_mock.get_request().content) == {
|
|
37
|
-
"name": "my-model",
|
|
40
|
+
"name": "my-model",
|
|
41
|
+
"description": "d",
|
|
38
42
|
}
|
|
39
43
|
|
|
40
44
|
|
|
41
45
|
def test_create_model_idempotent_on_409(httpx_mock, api):
|
|
42
46
|
httpx_mock.add_response(
|
|
43
|
-
url="http://test/api/v1/agents/me/models",
|
|
44
|
-
|
|
47
|
+
url="http://test/api/v1/agents/me/models",
|
|
48
|
+
method="POST",
|
|
49
|
+
status_code=409,
|
|
50
|
+
json={"detail": "exists"},
|
|
45
51
|
)
|
|
46
52
|
assert api.create_model("dup") == {"name": "dup", "status": "already_exists"}
|
|
47
53
|
|
|
48
54
|
|
|
49
55
|
def test_create_model_reraises_non_409(httpx_mock, api):
|
|
50
56
|
httpx_mock.add_response(
|
|
51
|
-
url="http://test/api/v1/agents/me/models",
|
|
52
|
-
|
|
57
|
+
url="http://test/api/v1/agents/me/models",
|
|
58
|
+
method="POST",
|
|
59
|
+
status_code=400,
|
|
60
|
+
json={"detail": "bad"},
|
|
53
61
|
)
|
|
54
62
|
with pytest.raises(EverestError):
|
|
55
63
|
api.create_model("x")
|
|
@@ -57,7 +65,8 @@ def test_create_model_reraises_non_409(httpx_mock, api):
|
|
|
57
65
|
|
|
58
66
|
def test_get_models(httpx_mock, api):
|
|
59
67
|
httpx_mock.add_response(
|
|
60
|
-
url="http://test/api/v1/agents/me/models",
|
|
68
|
+
url="http://test/api/v1/agents/me/models",
|
|
69
|
+
method="GET",
|
|
61
70
|
json={"agent_id": "a", "models": []},
|
|
62
71
|
)
|
|
63
72
|
assert api.get_models()["models"] == []
|
|
@@ -65,13 +74,16 @@ def test_get_models(httpx_mock, api):
|
|
|
65
74
|
|
|
66
75
|
# -- client: versioned dataset download (the #558 fix) -------------------
|
|
67
76
|
|
|
77
|
+
|
|
68
78
|
def test_download_dataset_uses_versioned_route(httpx_mock, api, tmp_path):
|
|
69
79
|
httpx_mock.add_response(
|
|
70
80
|
url="http://test/api/v1/data/download/bregen/futures/train",
|
|
71
81
|
content=b"PAR1data",
|
|
72
82
|
)
|
|
73
83
|
out = api.download_dataset(
|
|
74
|
-
universe="futures",
|
|
84
|
+
universe="futures",
|
|
85
|
+
split="train",
|
|
86
|
+
version="bregen",
|
|
75
87
|
output_path=str(tmp_path / "t.parquet"),
|
|
76
88
|
)
|
|
77
89
|
assert httpx_mock.get_request().url.path == "/api/v1/data/download/bregen/futures/train"
|
|
@@ -89,7 +101,9 @@ def test_download_dataset_latest_resolves_version(httpx_mock, api, tmp_path):
|
|
|
89
101
|
content=b"PAR1live",
|
|
90
102
|
)
|
|
91
103
|
out = api.download_dataset(
|
|
92
|
-
universe="futures",
|
|
104
|
+
universe="futures",
|
|
105
|
+
split="live",
|
|
106
|
+
version="latest",
|
|
93
107
|
output_path=str(tmp_path / "l.parquet"),
|
|
94
108
|
)
|
|
95
109
|
with open(out, "rb") as f:
|
|
@@ -98,12 +112,18 @@ def test_download_dataset_latest_resolves_version(httpx_mock, api, tmp_path):
|
|
|
98
112
|
|
|
99
113
|
# -- MCP server -----------------------------------------------------------
|
|
100
114
|
|
|
115
|
+
|
|
101
116
|
def test_mcp_tools_include_loop_and_model_tools():
|
|
102
117
|
from everestapi.mcp import server
|
|
118
|
+
|
|
103
119
|
names = {t["name"] for t in server.TOOLS}
|
|
104
120
|
for needed in (
|
|
105
|
-
"download_dataset",
|
|
106
|
-
"
|
|
121
|
+
"download_dataset",
|
|
122
|
+
"create_model",
|
|
123
|
+
"get_models",
|
|
124
|
+
"submit_futures_predictions",
|
|
125
|
+
"get_current_round",
|
|
126
|
+
"get_scores",
|
|
107
127
|
):
|
|
108
128
|
assert needed in names, needed
|
|
109
129
|
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|