-
Notifications
You must be signed in to change notification settings - Fork 64
Expand file tree
/
Copy pathclassifier.py
More file actions
140 lines (111 loc) · 3.92 KB
/
classifier.py
File metadata and controls
140 lines (111 loc) · 3.92 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
"""
Claude classification engine — replaces probability estimation with direction classification.
Asks "does this news confirm or deny the market question?" instead of "what's the probability?"
"""
from __future__ import annotations
import json
import time
import logging
from dataclasses import dataclass
import anthropic
import config
from markets import Market
log = logging.getLogger(__name__)
client = anthropic.Anthropic(api_key=config.ANTHROPIC_API_KEY)
CLASSIFICATION_PROMPT = """You are a news classifier for prediction markets.
## Market Question
{question}
## Current Market Price
YES: {yes_price:.2f} (implied probability: {yes_price:.0%})
## Breaking News
{headline}
Source: {source}
## Task
Does this news make the market question MORE likely to resolve YES, MORE likely to resolve NO, or is it NOT RELEVANT?
Also rate the MATERIALITY — how much should this move the price? 0.0 means no impact, 1.0 means this is definitive evidence.
Respond with ONLY valid JSON:
{{
"direction": "bullish" | "bearish" | "neutral",
"materiality": <float 0.0 to 1.0>,
"reasoning": "<1 sentence>"
}}"""
@dataclass
class Classification:
direction: str # "bullish", "bearish", "neutral"
materiality: float # 0.0-1.0
reasoning: str
latency_ms: int
model: str
def classify(headline: str, market: Market, source: str = "unknown") -> Classification:
"""Classify a news headline against a market question. Synchronous."""
start = time.time()
prompt = CLASSIFICATION_PROMPT.format(
question=market.question,
yes_price=market.yes_price,
headline=headline,
source=source,
)
try:
response = client.messages.create(
model=config.CLASSIFICATION_MODEL,
max_tokens=200,
temperature=0.1,
messages=[{"role": "user", "content": prompt}],
)
text = response.content[0].text.strip()
# Extract JSON
if "```" in text:
text = text.split("```")[1]
if text.startswith("json"):
text = text[4:]
text = text.strip()
result = json.loads(text)
latency = int((time.time() - start) * 1000)
direction = result.get("direction", "neutral")
if direction not in ("bullish", "bearish", "neutral"):
direction = "neutral"
materiality = max(0.0, min(1.0, float(result.get("materiality", 0))))
return Classification(
direction=direction,
materiality=materiality,
reasoning=result.get("reasoning", ""),
latency_ms=latency,
model=config.CLASSIFICATION_MODEL,
)
except Exception as e:
latency = int((time.time() - start) * 1000)
log.warning(f"[classifier] Error: {e}")
return Classification(
direction="neutral",
materiality=0.0,
reasoning=f"Classification error: {type(e).__name__}",
latency_ms=latency,
model=config.CLASSIFICATION_MODEL,
)
async def classify_async(headline: str, market: Market, source: str = "unknown") -> Classification:
"""Async wrapper around classify()."""
import asyncio
return await asyncio.get_event_loop().run_in_executor(
None, classify, headline, market, source
)
if __name__ == "__main__":
test_market = Market(
condition_id="test",
question="Will OpenAI release GPT-5 before August 2026?",
category="ai",
yes_price=0.62,
no_price=0.38,
volume=500000,
end_date="2026-08-01",
active=True,
tokens=[],
)
result = classify(
headline="OpenAI reportedly testing GPT-5 internally with select partners",
market=test_market,
source="The Information",
)
print(f"Direction: {result.direction}")
print(f"Materiality: {result.materiality}")
print(f"Reasoning: {result.reasoning}")
print(f"Latency: {result.latency_ms}ms")