first commit
This commit is contained in:
@@ -0,0 +1,125 @@
|
||||
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)
|
||||
Reference in New Issue
Block a user