I Built an AI Trading Bot That Watches Trump's Truth Social Posts
Paper trading worked great. Real markets taught me that latency kills everything.
It started the way these things always do. I was watching the market one afternoon and saw a stock move right after a Truth Social post. Not a subtle drift, either. A real move. And I thought: what if I could catch that automatically?
Not in a “get rich quick” way. More like a genuine curiosity question. Could an AI read a social media post, figure out which sectors it might affect, and place a trade before the market fully priced it in? I wanted to test the idea. So I built a bot to find out.
What it does
The bot monitors Trump’s Truth Social posts via an RSS feed, sends each post to Claude for sentiment analysis, runs the result through a 9-layer risk management system, and if everything checks out, executes trades through Alpaca’s API. The whole pipeline runs in a loop, polling every 30 seconds.
Claude reads the post and returns a structured analysis: is this market-relevant? What’s the sentiment? Which sectors are affected? Should we buy, sell, or hold? The response maps to specific ETFs across 19 sectors, from technology (XLK) to defense (ITA) to crypto (BITO). The full source is on GitHub if you want to poke around.
If the market is closed when a post comes in, the analysis gets queued and the trades execute when the market opens.
The stack
Python was the obvious choice here. The trading ecosystem in Python is mature, and Alpaca’s SDK (alpaca-py) made the brokerage integration straightforward. Anthropic’s SDK handles the Claude calls. SQLite stores the audit trail because I wanted every decision logged without needing to spin up a database server.
The whole thing runs in Docker. One container, one docker-compose up -d, and it’s watching.
I used Pydantic for config management and data validation. Every environment variable, every risk parameter, every API response gets validated before anything touches real money. Structlog handles logging with enough context to trace any trade back to the original post that triggered it.
The prompt that drives the whole thing
Getting Claude to return consistent, actionable analysis was the core challenge. The system prompt has to be specific enough to produce structured JSON every time, but flexible enough to handle the unpredictable nature of political social media posts.
SYSTEM_PROMPT = f"""You are a financial analyst AI. Your job is to analyze social media posts
from the President of the United States and determine their potential impact on financial markets.
You must evaluate each post and return a structured analysis. Be conservative in your assessments.
Only mark posts as relevant if they clearly relate to economic policy, trade, specific industries,
regulations, international relations affecting markets, or similar market-moving topics.
Posts about personal matters, birthdays, endorsements of non-market candidates, or general
political commentary without clear economic implications should be marked as NOT relevant.
...
Be specific about which sectors are affected. If the impact is broad/unclear, use "broad_market".
Only suggest "buy" or "sell" when you have reasonable confidence. Default to "hold" when uncertain."""
That “default to hold when uncertain” line does a lot of heavy lifting. Without it, Claude tends to find market relevance in everything.
Nine ways to say no
The risk management system was something I was genuinely proud of. Before any trade executes, it has to pass through nine sequential checks. Fail any one of them and the trade gets rejected.
def evaluate(self, analysis, post_id, portfolio_value, current_positions):
if analysis.confidence < settings.confidence_threshold:
return []
if not analysis.is_relevant:
return []
if analysis.direction == "hold":
return []
if self.last_trade_time:
elapsed = (datetime.now() - self.last_trade_time).total_seconds()
if elapsed < settings.trade_cooldown_seconds:
return []
if current_positions >= settings.max_open_positions:
return []
trades_today = self.db.get_trades_today()
if self._daily_loss_exceeded(trades_today, portfolio_value):
return []
Confidence threshold, relevance check, direction check, cooldown timer, position limits, daily loss limit, equity floor, buying power buffer, and short exposure cap. Short positions get extra scrutiny: 3% of portfolio max per position versus 5% for longs, tighter stop losses (2% vs 3%), and a 15% cap on total short exposure.
Every rejection gets logged with the specific reason. I wanted to be able to look back and understand exactly why a trade didn’t happen.
Gap protection
One thing that kept me up at night was gap risk. What happens when a post drops at 8pm, the bot queues a trade, and by the time the market opens, the price has already gapped way past where the stop loss should have been?
def check_gap_positions(self, db):
positions = self.client.get_all_positions()
closed = []
multiplier = settings.gap_protection_multiplier
for position in positions:
entry_price = float(position.avg_entry_price)
current_price = float(position.current_price)
gap_threshold = stop_pct * multiplier
if is_long and current_price <= entry_price * (1 - gap_threshold):
self.client.close_position(symbol_or_asset_id=symbol)
closed.append(symbol)
return closed
The gap protection runs every tick during market hours. If a position has moved 1.5x past its stop loss percentage, it force-closes the position immediately. It’s a safety net for the scenario where Alpaca’s bracket order stops haven’t triggered because the price jumped right past them.
What went wrong
Paper trading worked beautifully. Claude’s analysis was often right. The sector mapping made sense. The risk system caught bad trades. On paper, the strategy was profitable.
Then I turned on real money and learned a painful lesson about latency.
The problem is the pipeline. The RSS feed updates every 30 seconds or so. Then Claude needs a few seconds to analyze the post. Then the trade needs to submit and fill. By the time all of that happens, the market has already moved. The people who profit from these posts are the ones who see them in real time, not 30-45 seconds later.
Paper trading doesn’t punish you for this. Paper fills happen at the price you request. Real markets don’t work that way. The slippage between what I expected to pay and what I actually paid ate into every trade. A move that looked like a 2% gain on paper turned into a 0.5% gain or even a loss in practice.
The analysis was often correct. Claude would correctly identify that a post about tariffs was bearish for emerging markets, and EEM would indeed drop. But by the time my order filled, the easy money was already gone.
Where it is now
The bot still runs on paper trading. It’s a genuinely interesting system for studying how social media sentiment translates to market movements. The SQLite database has become a decent dataset for analyzing which types of posts actually move markets and which ones are noise.
As a real trading system, it needs a faster data source to be viable. The strategy itself isn’t broken. The execution speed is.
What I’d do differently
Ditch the RSS feed entirely. RSS was convenient but it’s fundamentally too slow for this use case. Some kind of direct web scraping or websocket connection to Truth Social would cut the detection time from 30+ seconds to near-instant. That alone might make the difference between profitable and not.
Pre-build analysis patterns. Claude is smart, but it’s also slow relative to market speed. If I could build a pattern library from historical posts (tariff mentions = short EEM, deregulation mentions = long XLF), I could skip the API call entirely for common post types and only use Claude for genuinely novel content.
Start with limit orders instead of market orders. Market orders during volatile moments guarantee bad fills. Limit orders might mean some trades don’t execute, but the ones that do would be at prices that actually make the strategy work.
The core idea, using AI to parse social media for market signals, isn’t wrong. The execution just needs to be measured in milliseconds, not seconds. And that’s a fundamentally different engineering problem than the one I solved.
Share this post
Stay in the loop
Get notified when I publish new posts. No spam, unsubscribe anytime.