xrtm-data 0.7.0__tar.gz → 0.7.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.
- {xrtm_data-0.7.0/src/xrtm_data.egg-info → xrtm_data-0.7.2}/PKG-INFO +1 -1
- {xrtm_data-0.7.0 → xrtm_data-0.7.2}/pyproject.toml +1 -1
- {xrtm_data-0.7.0 → xrtm_data-0.7.2}/src/xrtm/data/corpora/real_binary.py +10 -1
- {xrtm_data-0.7.0 → xrtm_data-0.7.2}/src/xrtm/data/providers/online/kalshi.py +1 -1
- {xrtm_data-0.7.0 → xrtm_data-0.7.2}/src/xrtm/data/providers/online/manifold.py +1 -2
- {xrtm_data-0.7.0 → xrtm_data-0.7.2}/src/xrtm/data/providers/online/polymarket.py +3 -2
- {xrtm_data-0.7.0 → xrtm_data-0.7.2/src/xrtm_data.egg-info}/PKG-INFO +1 -1
- {xrtm_data-0.7.0 → xrtm_data-0.7.2}/tests/test_online_sources.py +57 -0
- {xrtm_data-0.7.0 → xrtm_data-0.7.2}/tests/test_real_binary_corpus.py +13 -0
- {xrtm_data-0.7.0 → xrtm_data-0.7.2}/LICENSE +0 -0
- {xrtm_data-0.7.0 → xrtm_data-0.7.2}/README.md +0 -0
- {xrtm_data-0.7.0 → xrtm_data-0.7.2}/setup.cfg +0 -0
- {xrtm_data-0.7.0 → xrtm_data-0.7.2}/src/xrtm/data/__init__.py +0 -0
- {xrtm_data-0.7.0 → xrtm_data-0.7.2}/src/xrtm/data/core/__init__.py +0 -0
- {xrtm_data-0.7.0 → xrtm_data-0.7.2}/src/xrtm/data/core/interfaces.py +0 -0
- {xrtm_data-0.7.0 → xrtm_data-0.7.2}/src/xrtm/data/core/schemas/__init__.py +0 -0
- {xrtm_data-0.7.0 → xrtm_data-0.7.2}/src/xrtm/data/core/schemas/forecast.py +0 -0
- {xrtm_data-0.7.0 → xrtm_data-0.7.2}/src/xrtm/data/core/schemas/market.py +0 -0
- {xrtm_data-0.7.0 → xrtm_data-0.7.2}/src/xrtm/data/core/schemas/prior.py +0 -0
- {xrtm_data-0.7.0 → xrtm_data-0.7.2}/src/xrtm/data/core/schemas/resolution.py +0 -0
- {xrtm_data-0.7.0 → xrtm_data-0.7.2}/src/xrtm/data/core/schemas/trade.py +0 -0
- {xrtm_data-0.7.0 → xrtm_data-0.7.2}/src/xrtm/data/corpora/__init__.py +0 -0
- {xrtm_data-0.7.0 → xrtm_data-0.7.2}/src/xrtm/data/corpora/_builtin_corpora.py +0 -0
- {xrtm_data-0.7.0 → xrtm_data-0.7.2}/src/xrtm/data/corpora/registry.py +0 -0
- {xrtm_data-0.7.0 → xrtm_data-0.7.2}/src/xrtm/data/providers/__init__.py +0 -0
- {xrtm_data-0.7.0 → xrtm_data-0.7.2}/src/xrtm/data/providers/online/__init__.py +0 -0
- {xrtm_data-0.7.0 → xrtm_data-0.7.2}/src/xrtm/data/providers/online/metaculus.py +0 -0
- {xrtm_data-0.7.0 → xrtm_data-0.7.2}/src/xrtm/data/version.py +0 -0
- {xrtm_data-0.7.0 → xrtm_data-0.7.2}/src/xrtm_data.egg-info/SOURCES.txt +0 -0
- {xrtm_data-0.7.0 → xrtm_data-0.7.2}/src/xrtm_data.egg-info/dependency_links.txt +0 -0
- {xrtm_data-0.7.0 → xrtm_data-0.7.2}/src/xrtm_data.egg-info/requires.txt +0 -0
- {xrtm_data-0.7.0 → xrtm_data-0.7.2}/src/xrtm_data.egg-info/top_level.txt +0 -0
- {xrtm_data-0.7.0 → xrtm_data-0.7.2}/tests/test_corpus_registry.py +0 -0
- {xrtm_data-0.7.0 → xrtm_data-0.7.2}/tests/test_market_snapshot.py +0 -0
- {xrtm_data-0.7.0 → xrtm_data-0.7.2}/tests/test_prior_schemas.py +0 -0
- {xrtm_data-0.7.0 → xrtm_data-0.7.2}/tests/test_resolution_schemas.py +0 -0
- {xrtm_data-0.7.0 → xrtm_data-0.7.2}/tests/test_schemas.py +0 -0
|
@@ -33,7 +33,7 @@ from typing import Any, Mapping, Optional, Sequence
|
|
|
33
33
|
|
|
34
34
|
from pydantic import BaseModel, ConfigDict, Field, model_validator
|
|
35
35
|
|
|
36
|
-
from xrtm.data.core import DataSource
|
|
36
|
+
from xrtm.data.core import DataSource, SourceTemporalIntegrityError
|
|
37
37
|
from xrtm.data.core.schemas import ForecastQuestion, MetadataBase
|
|
38
38
|
|
|
39
39
|
REAL_BINARY_CORPUS_ID = "xrtm-real-binary-v1"
|
|
@@ -498,6 +498,11 @@ class RealBinaryCorpusSource(DataSource):
|
|
|
498
498
|
self, query: Optional[str] = None, limit: int = 5, *, snapshot_time: Optional[datetime] = None
|
|
499
499
|
) -> list[ForecastQuestion]:
|
|
500
500
|
r"""Fetch deterministic corpus questions, optionally filtering title/content text."""
|
|
501
|
+
if snapshot_time is not None:
|
|
502
|
+
raise SourceTemporalIntegrityError(
|
|
503
|
+
"RealBinaryCorpusSource cannot honour snapshot_time; it returns the embedded corpus "
|
|
504
|
+
"as-is. Use a time-aware source or omit the snapshot."
|
|
505
|
+
)
|
|
501
506
|
query_lower = query.lower() if query else None
|
|
502
507
|
matches: list[ForecastQuestion] = []
|
|
503
508
|
for question in self._questions:
|
|
@@ -512,6 +517,10 @@ class RealBinaryCorpusSource(DataSource):
|
|
|
512
517
|
self, question_id: str, *, snapshot_time: Optional[datetime] = None
|
|
513
518
|
) -> Optional[ForecastQuestion]:
|
|
514
519
|
r"""Retrieve a deterministic corpus question by id."""
|
|
520
|
+
if snapshot_time is not None:
|
|
521
|
+
raise SourceTemporalIntegrityError(
|
|
522
|
+
"RealBinaryCorpusSource cannot honour snapshot_time; it returns the embedded corpus as-is."
|
|
523
|
+
)
|
|
515
524
|
question = self._questions_by_id.get(question_id)
|
|
516
525
|
if question is None:
|
|
517
526
|
return None
|
|
@@ -177,7 +177,7 @@ class KalshiSource(DataSource, ResolutionSource):
|
|
|
177
177
|
title=title[:500] or ticker,
|
|
178
178
|
description=f"{subtitle[:500]}\n\n{price_info}".strip(),
|
|
179
179
|
metadata=MetadataBase(
|
|
180
|
-
snapshot_time=
|
|
180
|
+
snapshot_time=datetime.now(timezone.utc), # fetch time; close_time is in the future
|
|
181
181
|
source_version="kalshi",
|
|
182
182
|
tags=["kalshi", "binary", "prediction-market"],
|
|
183
183
|
raw_data={
|
|
@@ -163,14 +163,13 @@ class ManifoldSource(DataSource, ResolutionSource):
|
|
|
163
163
|
market_id = str(item.get("id", ""))
|
|
164
164
|
probability = item.get("probability")
|
|
165
165
|
price_info = f"Current probability: {float(probability)*100:.1f}%" if probability is not None else ""
|
|
166
|
-
close_time = item.get("closeTime")
|
|
167
166
|
|
|
168
167
|
return ForecastQuestion(
|
|
169
168
|
id=f"manifold-{market_id}",
|
|
170
169
|
title=str(item.get("question", ""))[:500],
|
|
171
170
|
description=f"Manifold market {market_id}. {price_info}".strip(),
|
|
172
171
|
metadata=MetadataBase(
|
|
173
|
-
snapshot_time=
|
|
172
|
+
snapshot_time=datetime.now(timezone.utc), # fetch time; close_time is in the future
|
|
174
173
|
source_version="manifold",
|
|
175
174
|
tags=["manifold", "binary", "prediction-market"],
|
|
176
175
|
raw_data={
|
|
@@ -79,8 +79,9 @@ class PolymarketSource(DataSource, ResolutionSource):
|
|
|
79
79
|
try:
|
|
80
80
|
data = self._get_json(url)
|
|
81
81
|
except DataSourceError:
|
|
82
|
-
#
|
|
83
|
-
|
|
82
|
+
# Fallback: same markets endpoint without the volume ordering,
|
|
83
|
+
# which some Gamma deployments reject.
|
|
84
|
+
url = f"{self.api_base}/markets?limit={min(limit, 100)}&closed=false"
|
|
84
85
|
data = self._get_json(url)
|
|
85
86
|
|
|
86
87
|
# Gamma API may wrap in "markets" key or return list directly
|
|
@@ -28,6 +28,28 @@ def test_sources_implement_resolution_interface():
|
|
|
28
28
|
assert isinstance(source, ResolutionSource)
|
|
29
29
|
|
|
30
30
|
|
|
31
|
+
@pytest.mark.asyncio
|
|
32
|
+
async def test_manifold_snapshot_time_is_fetch_time(monkeypatch):
|
|
33
|
+
from datetime import datetime, timedelta, timezone
|
|
34
|
+
|
|
35
|
+
source = ManifoldSource()
|
|
36
|
+
monkeypatch.setattr(
|
|
37
|
+
source,
|
|
38
|
+
"_get_json",
|
|
39
|
+
lambda url: [
|
|
40
|
+
{"id": "abc", "question": "Will X?", "isResolved": False, "closeTime": 4102444800000}, # year 2100
|
|
41
|
+
],
|
|
42
|
+
)
|
|
43
|
+
|
|
44
|
+
questions = await source.fetch_questions(limit=1)
|
|
45
|
+
|
|
46
|
+
assert questions
|
|
47
|
+
snapshot = questions[0].metadata.snapshot_time
|
|
48
|
+
assert snapshot is not None
|
|
49
|
+
# Snapshot must be the fetch time, never the (future) close time.
|
|
50
|
+
assert snapshot <= datetime.now(timezone.utc) + timedelta(seconds=5)
|
|
51
|
+
|
|
52
|
+
|
|
31
53
|
@pytest.mark.asyncio
|
|
32
54
|
async def test_manifold_fetch_resolved_maps_yes_no(monkeypatch):
|
|
33
55
|
source = ManifoldSource()
|
|
@@ -172,3 +194,38 @@ async def test_kalshi_fetch_resolved_paginates_cursor(monkeypatch):
|
|
|
172
194
|
|
|
173
195
|
assert len(resolved) == 250
|
|
174
196
|
assert calls["count"] == 2
|
|
197
|
+
|
|
198
|
+
|
|
199
|
+
@pytest.mark.asyncio
|
|
200
|
+
async def test_polymarket_fetch_questions_fallback_uses_markets(monkeypatch):
|
|
201
|
+
from xrtm.data.core import DataSourceError
|
|
202
|
+
|
|
203
|
+
source = PolymarketSource()
|
|
204
|
+
calls = {"urls": []}
|
|
205
|
+
|
|
206
|
+
def _get_json(url):
|
|
207
|
+
calls["urls"].append(url)
|
|
208
|
+
if "order=volume24hr" in url:
|
|
209
|
+
raise DataSourceError("volume ordering unsupported")
|
|
210
|
+
return [
|
|
211
|
+
{
|
|
212
|
+
"id": "m1",
|
|
213
|
+
"question": "Will X happen?",
|
|
214
|
+
"description": "d",
|
|
215
|
+
"outcomes": '["Yes", "No"]',
|
|
216
|
+
"outcomePrices": '["0.6", "0.4"]',
|
|
217
|
+
"volume": 1000,
|
|
218
|
+
"liquidity": 500,
|
|
219
|
+
"endDate": "2026-12-31T00:00:00Z",
|
|
220
|
+
"slug": "will-x",
|
|
221
|
+
"closed": False,
|
|
222
|
+
}
|
|
223
|
+
]
|
|
224
|
+
|
|
225
|
+
monkeypatch.setattr(source, "_get_json", _get_json)
|
|
226
|
+
|
|
227
|
+
questions = await source.fetch_questions(limit=1)
|
|
228
|
+
|
|
229
|
+
assert len(calls["urls"]) == 2
|
|
230
|
+
assert "/markets?" in calls["urls"][1]
|
|
231
|
+
assert questions and questions[0].id == "polymarket-m1"
|
|
@@ -26,6 +26,19 @@ from xrtm.data.corpora import (
|
|
|
26
26
|
)
|
|
27
27
|
|
|
28
28
|
|
|
29
|
+
@pytest.mark.asyncio
|
|
30
|
+
async def test_real_binary_corpus_source_rejects_snapshot_time():
|
|
31
|
+
from datetime import datetime
|
|
32
|
+
|
|
33
|
+
from xrtm.data import SourceTemporalIntegrityError
|
|
34
|
+
|
|
35
|
+
source = RealBinaryCorpusSource()
|
|
36
|
+
with pytest.raises(SourceTemporalIntegrityError):
|
|
37
|
+
await source.fetch_questions(limit=1, snapshot_time=datetime(2026, 1, 1))
|
|
38
|
+
with pytest.raises(SourceTemporalIntegrityError):
|
|
39
|
+
await source.get_question_by_id("any", snapshot_time=datetime(2026, 1, 1))
|
|
40
|
+
|
|
41
|
+
|
|
29
42
|
def test_real_binary_corpus_shape_and_resolution_coverage():
|
|
30
43
|
records = load_real_binary_corpus()
|
|
31
44
|
|
|
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
|
|
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
|