numerapi 2.23.0.dev2__tar.gz → 2.23.0.dev3__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.
- {numerapi-2.23.0.dev2 → numerapi-2.23.0.dev3}/PKG-INFO +1 -1
- {numerapi-2.23.0.dev2 → numerapi-2.23.0.dev3}/numerapi/base_api.py +79 -0
- {numerapi-2.23.0.dev2 → numerapi-2.23.0.dev3}/numerapi.egg-info/PKG-INFO +1 -1
- {numerapi-2.23.0.dev2 → numerapi-2.23.0.dev3}/numerapi.egg-info/SOURCES.txt +1 -0
- {numerapi-2.23.0.dev2 → numerapi-2.23.0.dev3}/setup.py +1 -1
- {numerapi-2.23.0.dev2 → numerapi-2.23.0.dev3}/tests/test_base_api.py +63 -0
- numerapi-2.23.0.dev3/tests/test_cryptoapi.py +43 -0
- {numerapi-2.23.0.dev2 → numerapi-2.23.0.dev3}/LICENSE +0 -0
- {numerapi-2.23.0.dev2 → numerapi-2.23.0.dev3}/README.md +0 -0
- {numerapi-2.23.0.dev2 → numerapi-2.23.0.dev3}/numerapi/__init__.py +0 -0
- {numerapi-2.23.0.dev2 → numerapi-2.23.0.dev3}/numerapi/cli.py +0 -0
- {numerapi-2.23.0.dev2 → numerapi-2.23.0.dev3}/numerapi/cryptoapi.py +0 -0
- {numerapi-2.23.0.dev2 → numerapi-2.23.0.dev3}/numerapi/numerapi.py +0 -0
- {numerapi-2.23.0.dev2 → numerapi-2.23.0.dev3}/numerapi/py.typed +0 -0
- {numerapi-2.23.0.dev2 → numerapi-2.23.0.dev3}/numerapi/signalsapi.py +0 -0
- {numerapi-2.23.0.dev2 → numerapi-2.23.0.dev3}/numerapi/utils.py +0 -0
- {numerapi-2.23.0.dev2 → numerapi-2.23.0.dev3}/numerapi.egg-info/dependency_links.txt +0 -0
- {numerapi-2.23.0.dev2 → numerapi-2.23.0.dev3}/numerapi.egg-info/entry_points.txt +0 -0
- {numerapi-2.23.0.dev2 → numerapi-2.23.0.dev3}/numerapi.egg-info/requires.txt +0 -0
- {numerapi-2.23.0.dev2 → numerapi-2.23.0.dev3}/numerapi.egg-info/top_level.txt +0 -0
- {numerapi-2.23.0.dev2 → numerapi-2.23.0.dev3}/setup.cfg +0 -0
- {numerapi-2.23.0.dev2 → numerapi-2.23.0.dev3}/tests/test_cli.py +0 -0
- {numerapi-2.23.0.dev2 → numerapi-2.23.0.dev3}/tests/test_numerapi.py +0 -0
- {numerapi-2.23.0.dev2 → numerapi-2.23.0.dev3}/tests/test_signalsapi.py +0 -0
- {numerapi-2.23.0.dev2 → numerapi-2.23.0.dev3}/tests/test_utils.py +0 -0
|
@@ -14,6 +14,7 @@ import requests
|
|
|
14
14
|
from numerapi import utils
|
|
15
15
|
|
|
16
16
|
API_TOURNAMENT_URL = "https://api-tournament.numer.ai"
|
|
17
|
+
_DEFAULT_TOURNAMENT = object()
|
|
17
18
|
|
|
18
19
|
|
|
19
20
|
class Api:
|
|
@@ -620,6 +621,84 @@ class Api:
|
|
|
620
621
|
round_num = data["number"]
|
|
621
622
|
return round_num
|
|
622
623
|
|
|
624
|
+
def list_rounds(
|
|
625
|
+
self,
|
|
626
|
+
tournament: int | None | object = _DEFAULT_TOURNAMENT,
|
|
627
|
+
number: int | None = None,
|
|
628
|
+
target: str | None = None,
|
|
629
|
+
status: str | None = None,
|
|
630
|
+
limit: int | None = None,
|
|
631
|
+
) -> List[Dict]:
|
|
632
|
+
"""List rounds with the filters supported by the round resolver.
|
|
633
|
+
|
|
634
|
+
Args:
|
|
635
|
+
tournament (int, optional): tournament filter, defaults to the API
|
|
636
|
+
instance tournament. Pass `None` to omit the tournament filter
|
|
637
|
+
number (int, optional): round number filter
|
|
638
|
+
target (str, optional): round target filter
|
|
639
|
+
status (str, optional): round status filter. One of `upcoming`,
|
|
640
|
+
`open`, `resolving`, or `resolved`
|
|
641
|
+
limit (int, optional): maximum number of rounds to return
|
|
642
|
+
|
|
643
|
+
Returns:
|
|
644
|
+
list of dicts: round entries matching the provided filters
|
|
645
|
+
"""
|
|
646
|
+
query = """
|
|
647
|
+
query($tournament: Int
|
|
648
|
+
$number: Int
|
|
649
|
+
$target: String
|
|
650
|
+
$status: RoundStatus
|
|
651
|
+
$limit: Int) {
|
|
652
|
+
rounds(tournament: $tournament
|
|
653
|
+
number: $number
|
|
654
|
+
target: $target
|
|
655
|
+
status: $status
|
|
656
|
+
limit: $limit) {
|
|
657
|
+
id
|
|
658
|
+
tournament
|
|
659
|
+
number
|
|
660
|
+
target
|
|
661
|
+
closeTime
|
|
662
|
+
closeStakingTime
|
|
663
|
+
openTime
|
|
664
|
+
scoreTime
|
|
665
|
+
resolveTime
|
|
666
|
+
resolvedGeneral
|
|
667
|
+
resolvedStaking
|
|
668
|
+
payoutFactor
|
|
669
|
+
stakeThreshold
|
|
670
|
+
minCorrMultiplier
|
|
671
|
+
maxCorrMultiplier
|
|
672
|
+
defaultCorrMultiplier
|
|
673
|
+
minMmcMultiplier
|
|
674
|
+
maxMmcMultiplier
|
|
675
|
+
defaultMmcMultiplier
|
|
676
|
+
dataDatestamp
|
|
677
|
+
}
|
|
678
|
+
}
|
|
679
|
+
"""
|
|
680
|
+
if tournament is _DEFAULT_TOURNAMENT:
|
|
681
|
+
tournament = self.tournament_id if self.tournament_id else None
|
|
682
|
+
arguments = {
|
|
683
|
+
"tournament": tournament,
|
|
684
|
+
"number": number,
|
|
685
|
+
"target": target,
|
|
686
|
+
"status": None if status is None else status.upper(),
|
|
687
|
+
"limit": limit,
|
|
688
|
+
}
|
|
689
|
+
rounds = self.raw_query(query, arguments)["data"]["rounds"]
|
|
690
|
+
for round_info in rounds:
|
|
691
|
+
for field in [
|
|
692
|
+
"closeTime",
|
|
693
|
+
"closeStakingTime",
|
|
694
|
+
"openTime",
|
|
695
|
+
"scoreTime",
|
|
696
|
+
"resolveTime",
|
|
697
|
+
]:
|
|
698
|
+
utils.replace(round_info, field, utils.parse_datetime_string)
|
|
699
|
+
utils.replace(round_info, "payoutFactor", utils.parse_float_string)
|
|
700
|
+
return rounds
|
|
701
|
+
|
|
623
702
|
def set_bio(self, model_id: str, bio: str) -> bool:
|
|
624
703
|
"""Set bio field for a model id.
|
|
625
704
|
|
|
@@ -178,6 +178,69 @@ def test_submission_scores(api):
|
|
|
178
178
|
assert request_body["variables"]["distinctOnRound"] is True
|
|
179
179
|
|
|
180
180
|
|
|
181
|
+
@responses.activate
|
|
182
|
+
def test_list_rounds(api):
|
|
183
|
+
api.tournament_id = 11
|
|
184
|
+
data = {
|
|
185
|
+
"data": {
|
|
186
|
+
"rounds": [
|
|
187
|
+
{
|
|
188
|
+
"id": "round-1",
|
|
189
|
+
"tournament": 11,
|
|
190
|
+
"number": 123,
|
|
191
|
+
"target": "main",
|
|
192
|
+
"closeTime": "2026-03-27T00:00:00Z",
|
|
193
|
+
"closeStakingTime": "2026-03-26T12:00:00Z",
|
|
194
|
+
"openTime": "2026-03-20T00:00:00Z",
|
|
195
|
+
"scoreTime": "2026-03-29T00:00:00Z",
|
|
196
|
+
"resolveTime": "2026-04-01T00:00:00Z",
|
|
197
|
+
"resolvedGeneral": False,
|
|
198
|
+
"resolvedStaking": False,
|
|
199
|
+
"payoutFactor": "0.8",
|
|
200
|
+
"stakeThreshold": 0.1,
|
|
201
|
+
"minCorrMultiplier": 0.0,
|
|
202
|
+
"maxCorrMultiplier": 1.0,
|
|
203
|
+
"defaultCorrMultiplier": 0.5,
|
|
204
|
+
"minMmcMultiplier": 0.0,
|
|
205
|
+
"maxMmcMultiplier": 1.0,
|
|
206
|
+
"defaultMmcMultiplier": 0.5,
|
|
207
|
+
"dataDatestamp": 20260320,
|
|
208
|
+
}
|
|
209
|
+
]
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
responses.add(responses.POST, base_api.API_TOURNAMENT_URL, json=data)
|
|
213
|
+
|
|
214
|
+
res = api.list_rounds(number=123, target="main", status="open", limit=5)
|
|
215
|
+
|
|
216
|
+
assert len(res) == 1
|
|
217
|
+
assert isinstance(res[0]["closeTime"], datetime.datetime)
|
|
218
|
+
assert isinstance(res[0]["closeStakingTime"], datetime.datetime)
|
|
219
|
+
assert isinstance(res[0]["openTime"], datetime.datetime)
|
|
220
|
+
assert isinstance(res[0]["scoreTime"], datetime.datetime)
|
|
221
|
+
assert isinstance(res[0]["resolveTime"], datetime.datetime)
|
|
222
|
+
assert isinstance(res[0]["payoutFactor"], decimal.Decimal)
|
|
223
|
+
|
|
224
|
+
request_body = json.loads(responses.calls[0].request.body)
|
|
225
|
+
assert request_body["variables"]["tournament"] == 11
|
|
226
|
+
assert request_body["variables"]["number"] == 123
|
|
227
|
+
assert request_body["variables"]["target"] == "main"
|
|
228
|
+
assert request_body["variables"]["status"] == "OPEN"
|
|
229
|
+
assert request_body["variables"]["limit"] == 5
|
|
230
|
+
|
|
231
|
+
|
|
232
|
+
@responses.activate
|
|
233
|
+
def test_list_rounds_without_tournament_filter(api):
|
|
234
|
+
api.tournament_id = 11
|
|
235
|
+
data = {"data": {"rounds": []}}
|
|
236
|
+
responses.add(responses.POST, base_api.API_TOURNAMENT_URL, json=data)
|
|
237
|
+
|
|
238
|
+
api.list_rounds(tournament=None)
|
|
239
|
+
|
|
240
|
+
request_body = json.loads(responses.calls[0].request.body)
|
|
241
|
+
assert request_body["variables"]["tournament"] is None
|
|
242
|
+
|
|
243
|
+
|
|
181
244
|
@responses.activate
|
|
182
245
|
def test_pending_model_payouts(api):
|
|
183
246
|
api.token = ("", "")
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import decimal
|
|
2
|
+
from unittest.mock import patch
|
|
3
|
+
|
|
4
|
+
import pytest
|
|
5
|
+
|
|
6
|
+
import numerapi
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
@pytest.fixture(scope="function", name="api")
|
|
10
|
+
def api_fixture():
|
|
11
|
+
api = numerapi.CryptoAPI(verbosity="DEBUG")
|
|
12
|
+
return api
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
@patch("numerapi.cryptoapi.CryptoAPI.raw_query")
|
|
16
|
+
def test_get_leaderboard(mocked, api):
|
|
17
|
+
mocked.return_value = {
|
|
18
|
+
"data": {
|
|
19
|
+
"cryptosignalsLeaderboard": [
|
|
20
|
+
{
|
|
21
|
+
"nmrStaked": "13.0",
|
|
22
|
+
"rank": 1,
|
|
23
|
+
"username": "crypto_user",
|
|
24
|
+
"corrRep": 0.1,
|
|
25
|
+
"mmcRep": 0.2,
|
|
26
|
+
"return_1_day": 0.03,
|
|
27
|
+
"return_52_weeks": 0.4,
|
|
28
|
+
"return_13_weeks": 0.15,
|
|
29
|
+
}
|
|
30
|
+
]
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
lb = api.get_leaderboard(1)
|
|
35
|
+
|
|
36
|
+
assert len(lb) == 1
|
|
37
|
+
assert lb[0]["username"] == "crypto_user"
|
|
38
|
+
assert lb[0]["nmrStaked"] == decimal.Decimal("13.0")
|
|
39
|
+
mocked.assert_called_once()
|
|
40
|
+
args, kwargs = mocked.call_args
|
|
41
|
+
assert "cryptosignalsLeaderboard" in args[0]
|
|
42
|
+
assert args[1] == {"limit": 1, "offset": 0}
|
|
43
|
+
assert kwargs == {}
|
|
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
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|