done
This commit is contained in:
+151
-48
@@ -1,46 +1,134 @@
|
||||
import json
|
||||
import logging
|
||||
from typing import Optional
|
||||
|
||||
import anthropic
|
||||
|
||||
from openai import AsyncOpenAI
|
||||
from app.config import settings
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
from typing import Optional
|
||||
_client: Optional[anthropic.AsyncAnthropic] = None
|
||||
_client: Optional[AsyncOpenAI] = 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."
|
||||
)
|
||||
SYSTEM_PROMPT = """You are an expert macro trader analyzing Trump's Truth Social posts for real-time crypto trading signals.
|
||||
|
||||
USER_PROMPT_TEMPLATE = """Analyze this Trump post for cryptocurrency trading signals.
|
||||
CORE ASSUMPTION: Treat every post as BREAKING NEWS. Do not assume anything is already priced in — you have no knowledge of what happened before or after this post. Your job is to evaluate the post content only.
|
||||
|
||||
Post: {text}
|
||||
=== BULLISH TRANSMISSION CHAINS (BTC/ETH go UP) ===
|
||||
• Ceasefire / peace deal / de-escalation / conflict ending
|
||||
→ war-risk premium unwinds → oil stabilizes → USD softens → risk-on → BTC/ETH rally
|
||||
• Pro-crypto executive action / legislation / reserve
|
||||
→ regulatory tailwind + direct demand → BTC up immediately
|
||||
• Dollar weakness (deficit spending, tariff concessions, Fed rate cut signals)
|
||||
→ hard asset bid → BTC as digital gold
|
||||
• Trade deal / tariff reduction announcement
|
||||
→ global growth outlook improves → risk appetite rises → BTC/ETH up
|
||||
• Strait of Hormuz open / oil supply stable
|
||||
→ energy prices moderate → inflation fears drop → risk-on
|
||||
|
||||
Respond with JSON only:
|
||||
=== BEARISH TRANSMISSION CHAINS (BTC/ETH go DOWN) ===
|
||||
• War escalation / military strikes / ceasefire violation
|
||||
→ risk-off panic → crypto liquidations → sharp BTC/ETH drop
|
||||
• Strait of Hormuz threatened / blocked / mined
|
||||
→ oil price spike → inflation surge → Fed tightening fears → risk-off
|
||||
• SEC/DOJ crypto enforcement / new crypto regulation
|
||||
→ direct selling pressure → BTC/ETH down
|
||||
• Tariff escalation / new sanctions / trade war
|
||||
→ growth fears + USD safe-haven bid → crypto outflows
|
||||
• New military front / ally breakdown / nuclear threat
|
||||
→ extreme risk-off → everything sells
|
||||
|
||||
=== WHAT IS NOT RELEVANT ===
|
||||
• Retweets with only a URL and no added comment (RT: https://...)
|
||||
• Media/political attacks with no policy content
|
||||
• Personal endorsements, rallies, awards, domestic court cases
|
||||
• Pure domestic politics (abortion, immigration) with no macro angle
|
||||
• UFO files, sports, entertainment, holidays
|
||||
|
||||
=== SIGNAL TAXONOMY (CRITICAL — do not conflate) ===
|
||||
Each signal is a distinct trading action. Choose exactly one.
|
||||
|
||||
• "buy" — OPEN A NEW LONG. Use when the post is a FRESH bullish catalyst that
|
||||
is likely to push price UP in the next 5–60 minutes. Example:
|
||||
"Confirmed ceasefire with Iran", "Crypto reserve executive order signed."
|
||||
→ Expected move: price rises. Directional accuracy = price > entry.
|
||||
|
||||
• "sell" — CLOSE AN EXISTING LONG / DE-RISK. Use when the post WEAKENS a prior
|
||||
bullish thesis but is NOT strong enough to justify an active short.
|
||||
Think: "take profit / step aside." Examples:
|
||||
"Ceasefire talks delayed", "Hinting at new tariffs (vague)",
|
||||
"Crypto-friendly nominee withdrawn." Ambiguous bearish.
|
||||
→ Use ONLY if you would exit longs but not open shorts.
|
||||
→ Directional accuracy = price < entry (same as short, but lower conviction).
|
||||
|
||||
• "short"— OPEN A NEW SHORT. Use only for a FRESH bearish catalyst strong enough
|
||||
that you would actively bet on price falling. Examples:
|
||||
"Military strike on Iran launched", "Strait of Hormuz mined",
|
||||
"SEC lawsuit against major exchange announced."
|
||||
→ Expected move: price drops hard. Reserve for high-conviction bearish.
|
||||
|
||||
• "hold" — NO TRADE. Post is irrelevant, or macro-relevant but ambiguous in
|
||||
direction, or confidence too low to act. DEFAULT when in doubt.
|
||||
|
||||
Decision tree:
|
||||
1. Is there a concrete macro/crypto event? NO → hold.
|
||||
2. Is the direction clear? NO → hold.
|
||||
3. BULLISH: confidence ≥ 60 → buy. Else → hold.
|
||||
4. BEARISH: strong/explicit (war, ban, sanctions, enforcement) → short.
|
||||
weak/vague/partial walkback of bullish → sell.
|
||||
otherwise → hold.
|
||||
|
||||
NEVER use "sell" for a strong bearish event — that is "short".
|
||||
NEVER use "short" for a vague/ambiguous negative — that is "sell" or "hold".
|
||||
|
||||
=== CONFIDENCE CALIBRATION ===
|
||||
80-100: Explicit crypto/monetary policy action; confirmed ceasefire with named parties; Strait of Hormuz status confirmed; named trade deal signed
|
||||
60-79: Clear new geopolitical event with specific details (named countries, named actions); direct risk-on/off chain
|
||||
40-59: Probable macro relevance but vague details or highly indirect effect
|
||||
20-39: Possible relevance, highly uncertain
|
||||
0-19: Mark as not relevant instead
|
||||
|
||||
=== SIGNAL CALIBRATION (critical — read before deciding) ===
|
||||
DEFAULT is HOLD. Only 5–10% of posts should receive buy or short.
|
||||
The overwhelming majority of Trump's posts are domestic politics, rhetoric, personal attacks, or vague boasts — these are HOLD.
|
||||
|
||||
Use "buy" only when ALL of the following are true:
|
||||
1. The event is CONCRETE (named countries, named action, specific policy)
|
||||
2. The macro transmission chain to BTC/ETH is DIRECT and SHORT (< 2 steps)
|
||||
3. Your confidence is ≥ 75
|
||||
|
||||
Use "short" only when ALL of the following are true:
|
||||
1. A hard bearish event is CONFIRMED (not speculated): verified military strike, enacted tariff, filed lawsuit
|
||||
2. The transmission chain to BTC/ETH down is DIRECT
|
||||
3. Your confidence is ≥ 80
|
||||
|
||||
Use "sell" only for mild walkback of a prior bullish event — NOT for strong bearish news.
|
||||
|
||||
When in doubt between buy and hold → choose HOLD.
|
||||
When in doubt between short and sell → choose SELL or HOLD, not short.
|
||||
|
||||
Return ONLY valid JSON. No markdown.
|
||||
|
||||
Return ONLY valid JSON. No markdown."""
|
||||
|
||||
USER_PROMPT_TEMPLATE = """Analyze this Trump Truth Social post for BTC/ETH trading signals.
|
||||
Treat it as breaking news — evaluate only what is written.
|
||||
|
||||
POST TEXT:
|
||||
{text}
|
||||
|
||||
Respond with JSON:
|
||||
{{
|
||||
"relevant": <true if this post could affect crypto/macro markets>,
|
||||
"asset": "BTC" | "ETH" | null,
|
||||
"relevant": <true if post contains ANY concrete macro/geopolitical/crypto information>,
|
||||
"asset": "BTC" | "ETH" | "BOTH" | null,
|
||||
"sentiment": "bullish" | "bearish" | "neutral",
|
||||
"signal": "buy" | "sell" | "short" | "hold",
|
||||
"confidence": <0-100>,
|
||||
"reasoning": "<1-2 sentences explaining the signal and why>"
|
||||
"reasoning": "<What specific event is described → macro transmission chain → expected market direction → WHY this signal and NOT the adjacent one (e.g. 'short not sell because strike is confirmed', 'sell not short because only a walkback of prior bullish headline')>"
|
||||
}}
|
||||
|
||||
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
|
||||
If post is only a URL, only a retweet link, or purely personal/domestic with zero macro angle: set relevant=false, signal=hold, confidence=0."""
|
||||
|
||||
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."""
|
||||
ANALYSIS_VERSION = "v4-selective"
|
||||
|
||||
_FALLBACK = {
|
||||
"relevant": False,
|
||||
@@ -49,36 +137,50 @@ _FALLBACK = {
|
||||
"signal": "hold",
|
||||
"confidence": 0,
|
||||
"reasoning": "",
|
||||
"prefilter_reason": None,
|
||||
"analysis_version": ANALYSIS_VERSION,
|
||||
}
|
||||
|
||||
|
||||
def _get_client() -> anthropic.AsyncAnthropic:
|
||||
def _fallback(prefilter: Optional[str] = None, reasoning: str = "") -> dict:
|
||||
out = dict(_FALLBACK)
|
||||
out["prefilter_reason"] = prefilter
|
||||
out["reasoning"] = reasoning
|
||||
return out
|
||||
|
||||
|
||||
def _get_client() -> AsyncOpenAI:
|
||||
global _client
|
||||
if _client is None:
|
||||
_client = anthropic.AsyncAnthropic(api_key=settings.anthropic_api_key)
|
||||
_client = AsyncOpenAI(
|
||||
api_key=settings.ai_api_key,
|
||||
base_url=settings.ai_base_url,
|
||||
)
|
||||
return _client
|
||||
|
||||
|
||||
async def analyze_post(text: str) -> dict:
|
||||
"""Run Claude signal analysis on a Trump post.
|
||||
# Fast pre-filter: pure RT/URL with no content
|
||||
stripped = text.strip()
|
||||
if stripped.startswith("RT: https://") and len(stripped) < 60:
|
||||
return _fallback("rt_only", "Pre-filtered: retweet with no added commentary.")
|
||||
if stripped.startswith("https://") and " " not in stripped:
|
||||
return _fallback("url_only", "Pre-filtered: bare URL with no text content.")
|
||||
if not stripped:
|
||||
return _fallback("empty", "Pre-filtered: empty post body.")
|
||||
|
||||
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,
|
||||
response = await client.chat.completions.create(
|
||||
model=settings.ai_model,
|
||||
max_tokens=450,
|
||||
temperature=0.1,
|
||||
messages=[
|
||||
{
|
||||
"role": "user",
|
||||
"content": USER_PROMPT_TEMPLATE.format(text=text[:2000]),
|
||||
}
|
||||
{"role": "system", "content": SYSTEM_PROMPT},
|
||||
{"role": "user", "content": USER_PROMPT_TEMPLATE.format(text=text[:2000])},
|
||||
],
|
||||
)
|
||||
raw = message.content[0].text.strip()
|
||||
raw = response.choices[0].message.content.strip()
|
||||
|
||||
if raw.startswith("```"):
|
||||
lines = raw.split("\n")
|
||||
@@ -100,10 +202,12 @@ async def analyze_post(text: str) -> dict:
|
||||
relevant = bool(result.get("relevant", False))
|
||||
|
||||
asset = result.get("asset")
|
||||
if asset == "BOTH":
|
||||
asset = "BTC"
|
||||
if asset not in ("BTC", "ETH", None):
|
||||
asset = None
|
||||
|
||||
reasoning = str(result.get("reasoning", ""))[:500]
|
||||
reasoning = str(result.get("reasoning", ""))[:600]
|
||||
|
||||
return {
|
||||
"relevant": relevant,
|
||||
@@ -112,14 +216,13 @@ async def analyze_post(text: str) -> dict:
|
||||
"signal": signal,
|
||||
"confidence": confidence,
|
||||
"reasoning": reasoning,
|
||||
"prefilter_reason": None,
|
||||
"analysis_version": ANALYSIS_VERSION,
|
||||
}
|
||||
|
||||
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)
|
||||
logger.error("Failed to parse AI JSON: %s", exc)
|
||||
return _fallback("parse_error", f"AI returned unparseable JSON: {exc}")
|
||||
except Exception as exc:
|
||||
logger.error("Unexpected error in analyze_post: %s", exc)
|
||||
return dict(_FALLBACK)
|
||||
logger.error("analyze_post error: %s", exc)
|
||||
return _fallback("api_error", f"AI API error: {exc}")
|
||||
|
||||
Reference in New Issue
Block a user