126 lines
3.8 KiB
Python
126 lines
3.8 KiB
Python
import json
|
|
import logging
|
|
|
|
import anthropic
|
|
|
|
from app.config import settings
|
|
|
|
logger = logging.getLogger(__name__)
|
|
|
|
from typing import Optional
|
|
_client: Optional[anthropic.AsyncAnthropic] = None
|
|
|
|
SYSTEM_PROMPT = (
|
|
"You are a crypto trading signal analyst. Analyze Donald Trump's social media posts "
|
|
"and determine their likely impact on cryptocurrency markets. "
|
|
"Return only valid JSON with no additional text or markdown."
|
|
)
|
|
|
|
USER_PROMPT_TEMPLATE = """Analyze this Trump post for cryptocurrency trading signals.
|
|
|
|
Post: {text}
|
|
|
|
Respond with JSON only:
|
|
{{
|
|
"relevant": <true if this post could affect crypto/macro markets>,
|
|
"asset": "BTC" | "ETH" | null,
|
|
"sentiment": "bullish" | "bearish" | "neutral",
|
|
"signal": "buy" | "sell" | "short" | "hold",
|
|
"confidence": <0-100>,
|
|
"reasoning": "<1-2 sentences explaining the signal and why>"
|
|
}}
|
|
|
|
Signal rules:
|
|
- "buy": bullish crypto/macro signal → expect price rise → go long
|
|
- "sell": bearish signal for existing longs → exit position
|
|
- "short": strong bearish signal → expect price drop → go short
|
|
- "hold": relevant but unclear direction, or neutral macro
|
|
|
|
Bullish examples: pro-crypto policy, BTC reserve, anti-regulation, dollar weakness, tariff deal, market optimism
|
|
Bearish examples: SEC crackdowns, war escalation, economic sanctions, crypto ban threats, crisis
|
|
Not relevant: personal attacks, sports, endorsements, unrelated politics
|
|
|
|
Be strict on relevance — most posts are NOT relevant to crypto."""
|
|
|
|
_FALLBACK = {
|
|
"relevant": False,
|
|
"asset": None,
|
|
"sentiment": "neutral",
|
|
"signal": "hold",
|
|
"confidence": 0,
|
|
"reasoning": "",
|
|
}
|
|
|
|
|
|
def _get_client() -> anthropic.AsyncAnthropic:
|
|
global _client
|
|
if _client is None:
|
|
_client = anthropic.AsyncAnthropic(api_key=settings.anthropic_api_key)
|
|
return _client
|
|
|
|
|
|
async def analyze_post(text: str) -> dict:
|
|
"""Run Claude signal analysis on a Trump post.
|
|
|
|
Returns dict with keys: relevant, asset, sentiment, signal, confidence, reasoning.
|
|
Falls back to neutral hold on any error.
|
|
"""
|
|
try:
|
|
client = _get_client()
|
|
message = await client.messages.create(
|
|
model="claude-haiku-4-5-20251001",
|
|
max_tokens=300,
|
|
system=SYSTEM_PROMPT,
|
|
messages=[
|
|
{
|
|
"role": "user",
|
|
"content": USER_PROMPT_TEMPLATE.format(text=text[:2000]),
|
|
}
|
|
],
|
|
)
|
|
raw = message.content[0].text.strip()
|
|
|
|
if raw.startswith("```"):
|
|
lines = raw.split("\n")
|
|
raw = "\n".join(lines[1:-1] if lines[-1].strip() == "```" else lines[1:])
|
|
|
|
result = json.loads(raw)
|
|
|
|
sentiment = result.get("sentiment", "neutral")
|
|
if sentiment not in ("bullish", "bearish", "neutral"):
|
|
sentiment = "neutral"
|
|
|
|
signal = result.get("signal", "hold")
|
|
if signal not in ("buy", "sell", "short", "hold"):
|
|
signal = "hold"
|
|
|
|
confidence = int(result.get("confidence", 0))
|
|
confidence = max(0, min(100, confidence))
|
|
|
|
relevant = bool(result.get("relevant", False))
|
|
|
|
asset = result.get("asset")
|
|
if asset not in ("BTC", "ETH", None):
|
|
asset = None
|
|
|
|
reasoning = str(result.get("reasoning", ""))[:500]
|
|
|
|
return {
|
|
"relevant": relevant,
|
|
"asset": asset,
|
|
"sentiment": sentiment,
|
|
"signal": signal,
|
|
"confidence": confidence,
|
|
"reasoning": reasoning,
|
|
}
|
|
|
|
except anthropic.APIError as exc:
|
|
logger.error("Anthropic API error: %s", exc)
|
|
return dict(_FALLBACK)
|
|
except json.JSONDecodeError as exc:
|
|
logger.error("Failed to parse Claude JSON response: %s", exc)
|
|
return dict(_FALLBACK)
|
|
except Exception as exc:
|
|
logger.error("Unexpected error in analyze_post: %s", exc)
|
|
return dict(_FALLBACK)
|