mirror of
https://github.com/TauricResearch/TradingAgents.git
synced 2026-06-16 21:06:15 +03:00
feat(data): add Polymarket prediction markets as a keyless vendor
Surface live, market-implied probabilities for forward-looking events (Fed decisions, recession, elections, geopolitics, crypto) to the news analyst via a new get_prediction_markets tool and a prediction_markets vendor category. Backed by Polymarket's public Gamma API (no key). Results are filtered to open, forward-looking markets (closed and past-dated events excluded), ranked by traded volume, and rendered with implied probability, volume, resolution date, and the recent move. External errors degrade to a clear unavailable message rather than interrupting the analyst.
This commit is contained in:
129
tests/test_polymarket.py
Normal file
129
tests/test_polymarket.py
Normal file
@@ -0,0 +1,129 @@
|
||||
"""Polymarket prediction-market vendor: forward-looking filtering, volume
|
||||
ranking, formatting, graceful degradation, and router integration.
|
||||
|
||||
All API access is mocked, so these run without a network connection.
|
||||
"""
|
||||
import copy
|
||||
import unittest
|
||||
from unittest import mock
|
||||
|
||||
import pytest
|
||||
import requests
|
||||
|
||||
import tradingagents.dataflows.config as config_module
|
||||
import tradingagents.default_config as default_config
|
||||
from tradingagents.dataflows import interface, polymarket
|
||||
from tradingagents.dataflows.config import set_config
|
||||
|
||||
|
||||
def _market(question, prob, *, volume, end_date, closed=False, wk=None):
|
||||
return {
|
||||
"question": question,
|
||||
"outcomes": '["Yes", "No"]',
|
||||
"outcomePrices": f'["{prob}", "{round(1 - prob, 4)}"]',
|
||||
"volumeNum": volume,
|
||||
"endDate": end_date,
|
||||
"closed": closed,
|
||||
"oneWeekPriceChange": wk,
|
||||
}
|
||||
|
||||
|
||||
# One event with a mix: a high-volume open market, a closed one, a past-dated
|
||||
# one, and a lower-volume open one. Far-future / far-past dates keep the test
|
||||
# independent of the real clock.
|
||||
_SEARCH = {
|
||||
"events": [
|
||||
{
|
||||
"markets": [
|
||||
_market("Open big?", 0.76, volume=5_000_000, end_date="2030-12-31T00:00:00Z", wk=-0.045),
|
||||
_market("Resolved already?", 1.0, volume=9_000_000, end_date="2030-12-31T00:00:00Z", closed=True),
|
||||
_market("Past event?", 0.5, volume=8_000_000, end_date="2020-01-01T00:00:00Z"),
|
||||
_market("Open small?", 0.30, volume=1_000, end_date="2030-06-30T00:00:00Z"),
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
|
||||
@pytest.mark.unit
|
||||
class PolymarketFilterTests(unittest.TestCase):
|
||||
def test_closed_and_past_markets_are_excluded(self):
|
||||
with mock.patch.object(polymarket, "_request", return_value=_SEARCH):
|
||||
out = polymarket.get_prediction_markets("anything", limit=10)
|
||||
self.assertIn("Open big?", out)
|
||||
self.assertIn("Open small?", out)
|
||||
self.assertNotIn("Resolved already?", out) # closed
|
||||
self.assertNotIn("Past event?", out) # endDate in the past
|
||||
|
||||
def test_ranked_by_volume(self):
|
||||
with mock.patch.object(polymarket, "_request", return_value=_SEARCH):
|
||||
out = polymarket.get_prediction_markets("anything", limit=10)
|
||||
self.assertLess(out.index("Open big?"), out.index("Open small?"))
|
||||
|
||||
def test_limit_caps_results(self):
|
||||
with mock.patch.object(polymarket, "_request", return_value=_SEARCH):
|
||||
out = polymarket.get_prediction_markets("anything", limit=1)
|
||||
self.assertIn("Open big?", out)
|
||||
self.assertNotIn("Open small?", out)
|
||||
|
||||
|
||||
@pytest.mark.unit
|
||||
class PolymarketFormatTests(unittest.TestCase):
|
||||
def test_probability_volume_and_weekly_change_render(self):
|
||||
with mock.patch.object(polymarket, "_request", return_value=_SEARCH):
|
||||
out = polymarket.get_prediction_markets("anything", limit=10)
|
||||
self.assertIn("Yes 76%", out)
|
||||
self.assertIn("$5,000,000 volume", out)
|
||||
self.assertIn("resolves 2030-12-31", out)
|
||||
self.assertIn("1-week -4.5pp", out) # -0.045 -> -4.5pp
|
||||
|
||||
def test_weekly_change_omitted_when_absent(self):
|
||||
# "Open small?" has wk=None -> no 1-week clause on its line.
|
||||
with mock.patch.object(polymarket, "_request", return_value=_SEARCH):
|
||||
out = polymarket.get_prediction_markets("anything", limit=10)
|
||||
small_line = next(ln for ln in out.splitlines() if "Open small?" in ln)
|
||||
self.assertNotIn("1-week", small_line)
|
||||
|
||||
def test_no_matches_reports_clearly(self):
|
||||
with mock.patch.object(polymarket, "_request", return_value={"events": []}):
|
||||
out = polymarket.get_prediction_markets("obscure ticker", limit=6)
|
||||
self.assertIn("No open prediction markets", out)
|
||||
|
||||
|
||||
@pytest.mark.unit
|
||||
class PolymarketResilienceTests(unittest.TestCase):
|
||||
def test_network_error_degrades_gracefully(self):
|
||||
# An external-service hiccup must not raise into the analyst.
|
||||
with mock.patch.object(
|
||||
polymarket, "_request", side_effect=requests.RequestException("boom")
|
||||
):
|
||||
out = polymarket.get_prediction_markets("Fed rate cut")
|
||||
self.assertIn("unavailable", out.lower())
|
||||
self.assertIn("Fed rate cut", out)
|
||||
|
||||
|
||||
@pytest.mark.unit
|
||||
class PolymarketRoutingTests(unittest.TestCase):
|
||||
def setUp(self):
|
||||
config_module._config = copy.deepcopy(default_config.DEFAULT_CONFIG)
|
||||
|
||||
def tearDown(self):
|
||||
config_module._config = copy.deepcopy(default_config.DEFAULT_CONFIG)
|
||||
|
||||
def test_category_routes_to_polymarket(self):
|
||||
self.assertEqual(
|
||||
interface.get_category_for_method("get_prediction_markets"),
|
||||
"prediction_markets",
|
||||
)
|
||||
set_config({"data_vendors": {"prediction_markets": "polymarket"}})
|
||||
with mock.patch.dict(
|
||||
interface.VENDOR_METHODS,
|
||||
{"get_prediction_markets": {"polymarket": lambda *a, **k: "POLY_OK"}},
|
||||
clear=False,
|
||||
):
|
||||
out = interface.route_to_vendor("get_prediction_markets", "fed", 5)
|
||||
self.assertEqual(out, "POLY_OK")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
Reference in New Issue
Block a user