← Back to blog
Trading Python Raspberry Pi
 ·  8 min read

Running a 24/7 trading bot on a Raspberry Pi 5: architecture and lessons learned

How I designed a resilient Python trading system that survives power outages, network drops, and API rate limits — running continuously on a low-power ARM device with under 5% CPU utilization.

“The stock market is a device for transferring money from the impatient to the patient.”

— Warren Buffett

Why a Raspberry Pi?

The premise was simple: build a system that generates consistent returns with minimal infrastructure cost. A Raspberry Pi 5 draws around 5W under normal load, runs Linux natively, costs under $100, and sits silently on a shelf. For a trading bot that needs to be always on but rarely compute-intensive, it's a near-perfect match.

The constraint-first design philosophy shaped every decision. Instead of cloud VMs and managed databases, the system runs on hardware I own, with services I control. The only external dependencies are the exchange API and AI inference endpoints.

Architecture overview

The bot is not a simple loop. It runs 9 concurrent threads, each with a specific responsibility and lifecycle:

Thread Frequency Responsibility
Main Loop Every 4h Technical analysis, regime detection, signal generation
Position Monitor 2–30s adaptive Real-time SL/TP and trailing stop management
MTF Monitor (P15) 30 min 1H timeframe confirmation for deferred BUY signals
Buy Monitor (P18) 30 min Detects BUY signals on 1H, feeds P15 queue
Exit Monitor (P17) 30 min Early exit detection on open positions
Pulse Scanner (P16) 4h Detects sharp moves across the full universe
Telegram Listener Long-poll Real-time commands (/status, /analyze, /ask, etc.)
Claude Advisor Weekly + trigger AI-driven strategy review and symbol rotation
Daily Auditor 22:00 daily Health check, DB audit, daily summary

All threads share mutable state (positions, capital, config) protected by a threading.RLock to prevent race conditions. Every access to shared resources takes a snapshot before processing.

The 4-hour cycle

The main cycle aligns with 4-hour candle closes (00:00, 04:00, 08:00 UTC−4). This is intentional: acting on a closed candle avoids the noise of in-progress bars and makes backtesting results reproducible.

For each symbol in the active portfolio, the cycle:

  1. Fetches recent 4H and 1D candles for higher-timeframe context
  2. Calculates indicators: RSI, SMA-20/50, ATR, Bollinger Bands, ADX
  3. Classifies the market regime: TRENDING, RANGING, or MIXED
  4. Selects the appropriate strategy based on regime
  5. Generates a BUY / SELL / HOLD signal with confidence score
  6. Validates through AI before execution

Auto market-regime strategy selection

The core insight is that no single strategy works in all market conditions. The bot dynamically selects among three strategies based on regime detection:

RSI Reversal (Ranging markets, ATR < 1.5%)

Buys oversold conditions (RSI < 35) with Bollinger Band lower-touch confirmation. Targets the mean reversion in sideways markets. Dynamic SL/TP at 1.5× and 2.5× ATR.

SMA Cross (Trending markets)

Enters on the 20/50 SMA crossover with ADX > 25 momentum confirmation. Rides trends with a wider SL (2× ATR) and aggressive TP (3× ATR). Avoids whipsaws in choppy conditions.

Combined / Voting (Mixed markets)

Both strategies must agree to generate a signal. Higher confidence threshold required. Acts as a conservative filter when regime is ambiguous.

Two-layer AI validation

Technical signals alone are not enough. The system adds two AI layers before any trade executes:

Layer 1: Per-signal validator (Gemini Flash)

Every BUY or SELL signal is sent to Gemini 2.0 Flash with full technical context plus the current Fear & Greed Index. The validator returns CONFIRM, REJECT, WAIT, or REDUCE_SIZE. This layer runs in real time and is optimized for speed and cost.

Layer 2: Weekly strategic advisor (Claude Opus)

Once a week (and after significant events), Claude Opus 4.6 receives a full performance report: win rate, P&L by symbol, recent signals, and backtest results across the 15-symbol universe. It recommends symbol rotations, strategy parameter adjustments, and risk threshold changes. The bot applies these automatically and notifies via Telegram.

Real example

The advisor rotated ARB/USDT out of the portfolio after 6 consecutive HOLD cycles and low backtest scores, replacing it with BTC/USDT (pinned) + NEAR/USDT based on higher trending momentum scores.

Trailing stop: 5 escalating phases

Once a position is open, the Position Monitor checks every 2–30 seconds (adaptive frequency based on risk phase) and manages a 5-phase trailing stop:

  1. Phase 0 — Fixed SL at entry (ATR-based)
  2. Phase 1 (Break-even) — If profit ≥ 0.5%, move SL to entry price. Never lose a winning position.
  3. Phase 2 (Immediate trail) — If profit ≥ 0.5×ATR, SL = peak − 0.5×ATR
  4. Phase 3 (Continuous trail) — If profit ≥ 2.0×ATR, maintains tight trail
  5. Phase 4 (Hard exit) — Maximum drawdown from peak threshold

As a safety net, stop-loss orders are also registered directly with eToro. If the bot goes offline, the exchange-side SL still protects the position.

State persistence and resilience

This is where most hobby trading bots fail silently. If the Pi loses power mid-write, a corrupted state file means the bot wakes up with no knowledge of open positions. The solution is atomic writes:

# Write to temp file first, then atomically rename
with open(f"{path}.tmp", "w") as f:
    json.dump(state, f, indent=2)
os.replace(f"{path}.tmp", path)  # atomic on POSIX

On startup, the bot checks for a .tmp file (indicating a crash mid-write) and falls back to the last known-good state. Capital, open positions, and counters are all recovered.

Trade history is persisted to both a local SQLite database and a MySQL instance on Hostinger simultaneously. If the remote DB is unreachable, the local write still succeeds and the MySQL mirror catches up on next connection.

Hardware performance

After months of continuous operation, the Pi 5 metrics are consistently:

Lesson learned

The Pi 5 runs significantly hotter than the Pi 4 under the same load. At 52°C+ the thermal governor starts throttling. A small heatsink on the SoC brought idle temps from 58°C down to 44°C without any fan noise.

An honest note on fees

After months of paper trading with real market data, the win rate consistently sits above 50% — the bot does what it was designed to do. Signals are sound, risk management works, and the trailing stop protects gains.

The uncomfortable truth, however, is that broker fees are the silent killer of small-account algorithmic trading. Every eToro operation carries a spread cost plus overnight fees for positions held more than a day. On a $50,000 demo account operating with conservative position sizing, the gross P&L is positive — but net of fees, the margin is razor thin.

Reality check

A trade that generates a 0.4% gross gain on a $1,000 position produces ~$4. eToro’s spread on crypto can consume $2–3 of that on entry and exit alone. The bot wins the trade; the broker wins the day. This is not unique to eToro — it’s the fundamental math of retail algorithmic trading at small scale.

The path to profitability is either larger capital (where fixed fees become a smaller percentage), less frequent but higher-conviction trades, or moving to a lower-fee venue. The current system is built for the first two: the AI validation layer specifically exists to reduce trade frequency and only act on high-confidence signals. The project remains in paper trading until the fee-adjusted win rate proves itself over a statistically significant sample.

Lessons learned

1. Thread safety is non-negotiable. The first version had no locks. Under high Telegram command load while the main cycle was running, positions would occasionally show stale data. Adding threading.RLock and snapshots eliminated the issue entirely.

2. Atomic state saves. A power outage revealed that a simple json.dump() open/write/close sequence is not safe. The file can be partially written. Switched to the write-to-tmp / rename pattern immediately.

3. Backtesting must use the same code path. Having a separate backtesting module led to subtle divergences between simulated and live behavior. The backtest now feeds data into the same StrategyEngine class the live bot uses.

4. Paper trade longer than you think. Six months in demo mode revealed three edge cases that only appear in specific market conditions (weekend gaps, API timeouts during volatility spikes, eToro rate limit bursts). Each one would have cost real money.

5. Build for the outage, not the happy path. Exchange API down? The bot retries with exponential backoff and logs the event. Telegram unreachable? Silently skips notification and resumes on next cycle. AI API credits exhausted? Falls back to the secondary provider automatically.

← Back to blog 🔗 View on GitHub