“The stock market is a device for transferring money from the impatient to the patient.”
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.
From one bot to three
What started as a single crypto bot on eToro demo has grown into three independent systemd services, each targeting a different market and exchange:
| Service | Market | Exchange | Symbols |
|---|---|---|---|
| trading-bot.service (BOT-AUTO) |
Crypto 24/7 | eToro demo + Binance testnet | BTC/USDT, LINK/USDT, AVAX/USDT, ATOM/USDT + ADA/USDT, NEAR/USDT on Binance |
| trading-bot-etf.service (ETF-BOT) |
NYSE hours | eToro demo (Yahoo Finance data) | SPY, XLE, DIA, XLF |
| trading-bot-forex.service (BOT-FOREX) |
Forex 24/5 | PaperExchange + Yahoo Finance | EUR/USD, USD/JPY, USD/CHF |
All three run on the same Raspberry Pi 5, in pure paper/demo mode — no real money involved. Capital was normalized to $500 per bot in August 2026 to allow fair performance comparisons across the three. The Forex bot uses a PaperExchange internally because eToro requires a CFD permission that is pending approval.
Architecture overview
Each bot is not a simple loop. It runs 9 concurrent threads, each with a specific responsibility and lifecycle. The table below reflects the current state of the crypto bot (BOT-AUTO); ETF and Forex bots share the same thread model:
| Thread | Frequency | Responsibility | Status |
|---|---|---|---|
| Main Loop | Every 4h | Technical analysis, regime detection, 4H signal generation | ✅ Active |
| Position Monitor | 2–30s adaptive | Real-time trailing stop and SL/TP management | ✅ Active |
| Buy Monitor (P18) | 30 min | Primary BUY route — independent 1H signals using momentum_1h strategy (96% of trades) |
✅ Active |
| Exit Monitor (P17) | 30 min | Early exit detection on open positions | ✅ Active |
| MTF Monitor (P15) | 30 min | 1H confirmation of 4H signals — superseded by P18 as the primary entry mechanism | ❌ Disabled (shadow mode) |
| Pulse Scanner (P16) | 4h | Detects sharp moves across the full symbol universe | ✅ Active |
| Telegram Listener | Long-poll | Real-time commands routed via telegram_router.service (/status, /analyze, /ask, etc.) |
✅ Active |
| Advisor (Claude / Gemini) | Weekly + 3 losses or 6 HOLDs | AI-driven strategy review and symbol rotation | ✅ Active |
| Daily Auditor | 22:00 daily | Health check, DB audit, daily summary with AI | ✅ Active |
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:
- Fetches recent 4H and 1D candles for higher-timeframe context
- Calculates indicators: RSI, SMA-20/50, ATR, Bollinger Bands, ADX
- Classifies the market regime: TRENDING, RANGING, or MIXED
- Selects the appropriate strategy based on regime
- Generates a BUY / SELL / HOLD signal with confidence score
- 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 strategies based on regime detection:
RSI Reversal (Ranging markets)
Buys oversold conditions with Bollinger Band lower-touch confirmation. Oversold threshold is market-specific: RSI < 30 for crypto, < 38 for ETFs, < 40 for Forex. Targets mean reversion in sideways markets. Dynamic SL/TP at 1.5× and 2.5× ATR.
Combined / Voting (Trending & Mixed markets)
Weighted votes from multiple indicators: SMA (±0.75), RSI (±0.65), optional Bollinger and MACD. Fires when total votes ≥ 0.65. Acts as the main strategy for both TRENDING and MIXED regimes (previously SMA Cross handled TRENDING separately).
momentum_1h (P18 / P17 — dominant strategy)
Native 1H strategy powering the Buy Monitor (P18) and Exit Monitor (P17): EMA9 > EMA21 + RSI in zone + volume confirmation. Responsible for ˜96% of all trades. Runs independently from the 4H main cycle, giving the bot an additional intraday entry layer.
Market-specific AI modifiers
Beyond the core strategies, each bot has additional signal modifiers: VIX for ETFs (adjusts confidence based on market-wide volatility), DXY for Forex (dollar direction adjusts signals depending on whether USD is the base or quote currency), and Pivot Points for Forex (daily support/resistance levels). Fibonacci retracement levels apply across all three bots.
Multi-layer AI validation
Technical signals alone are not enough. The system adds multiple AI layers before any trade executes:
Layer 1: Per-signal validator (Gemma 4 26B)
Every BUY or SELL signal is sent to Gemma 4 26B (default local model) with full technical context. The validator returns CONFIRM, REJECT, WAIT, or REDUCE_SIZE. This layer runs in real time and is optimized for speed and cost. Gemini serves as the cloud fallback when the local model is unavailable.
Layer 2: Weekly strategic advisor (Claude Opus 4.6)
Once a week — and after 3 consecutive losses or 6 consecutive HOLDs — Claude Opus 4.6 receives a full performance report: win rate, P&L by symbol, recent signals, and backtest results. It recommends symbol rotations, strategy parameter adjustments, and risk threshold changes. The bot applies these automatically and notifies via Telegram. Gemini acts as fallback.
Layer 3: On-demand assistant and daily auditor
A /ask Telegram command sends questions to Claude Haiku with live bot context (positions, recent signals, P&L) for instant Q&A. Separately, the Daily Auditor runs at 22:00 every day: it performs a health check, audits the database, and generates an AI summary of the day’s activity.
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: 4 escalating phases
Once a position is open, the Position Monitor checks every 2–30 seconds (adaptive frequency based on risk phase) and manages a 4-phase ATR-based trailing stop:
- Phase 1 (Break-even) — When profit ≥ 1.0×ATR: move SL to entry price + minimum margin. Never lose a winning position.
- Phase 2 — When profit ≥ 1.5×ATR: SL = peak − 1.5×ATR
- Phase 3 — When profit ≥ 2.5×ATR: SL = peak − 1.0×ATR (tighter trail)
- Phase 4 — When profit ≥ 4.0×ATR: SL = peak − 0.5×ATR (aggressive protection of gains)
As a safety net, stop-loss orders are also registered directly with the exchange. 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 running three bots simultaneously, the Pi 5 metrics are consistently:
- CPU: 0.4–3% (spikes to 15% during cycle processing)
- RAM: ~573MB used of 16GB available
- Temperature: 44–52°C (passive cooling, no fan)
- Uptime: 99.9%+ (only interrupted for OS updates)
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 results and fees
After months of paper trading with real market data, each bot tells a different story. The ETF bot leads with a 65.9% win rate and positive P&L (+$240 across 44 trades since May 2026). The crypto eToro bot sits at a breakeven-ish 50% win rate (130 trades since Feb 2026). The Forex bot is early-stage with only 5 trades. Binance testnet data is distorted by an inflated initial capital (~$75k on testnet) and early trades before the momentum_1h strategy stabilized — once P18 normalized, the last 61 trades show 65.6% win rate.
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 $500 demo account operating with conservative position sizing, the gross P&L may be positive — but net of fees, the margin is razor thin.
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.