Building CryptoOracle: Lessons from Shipping a Real Autonomous Trading Bot
Building CryptoOracle: Lessons from Shipping a Real Autonomous Trading Bot
In February 2026, I shipped CryptoOracle — an autonomous trading bot that runs 24/7 on a Mac mini, executes trades on Solana, and manages risk without human intervention.
As of this writing: $170+ in realized profit, 100% win rate across 8 trades.
This isn't a backtest. This is real money, real trades, real lessons.
The Problem
Manual crypto trading is brutal:
- Markets move 24/7 (no weekends, no holidays)
- Emotional discipline is hard when you're watching charts
- Entry timing matters — being 5 minutes late costs you 2-3%
I wanted a system that could:
- Generate signals based on technical analysis
- Execute trades automatically on Solana (via Jupiter DEX)
- Manage stop-loss and take-profit levels
- Alert me via Discord for every action
- Run continuously without babysitting
The Architecture
Here's the stack:
- Python (async/await for concurrency)
- Solana blockchain (fast, cheap transactions)
- Jupiter DEX (best swap rates, no registration)
- Pyth Network (real-time WebSocket price feeds)
- Discord webhooks (alerts for every trade)
The bot runs as a macOS LaunchAgent — it starts on boot, restarts on crash, and logs everything.
Signal Generation
The bot uses a vote-based system across multiple timeframes:
def generate_signal(prices: list[float], timeframes: list[str]) -> Signal:
votes = {"long": 0, "short": 0, "neutral": 0}
for tf in timeframes:
rsi = calculate_rsi(prices, period=14)
macd = calculate_macd(prices)
bb = calculate_bollinger_bands(prices)
# RSI oversold/overbought
if rsi < 30:
votes["long"] += 1
elif rsi > 70:
votes["short"] += 1
# MACD crossover
if macd["histogram"][-1] > 0 and macd["histogram"][-2] <= 0:
votes["long"] += 1
elif macd["histogram"][-1] < 0 and macd["histogram"][-2] >= 0:
votes["short"] += 1
# Bollinger Band bounce
if prices[-1] < bb["lower"]:
votes["long"] += 1
elif prices[-1] > bb["upper"]:
votes["short"] += 1
# Require 60% consensus
total_votes = sum(votes.values())
if votes["long"] / total_votes >= 0.6:
return Signal.LONG
elif votes["short"] / total_votes >= 0.6:
return Signal.SHORT
else:
return Signal.NEUTRAL
This prevents whipsaws. A single timeframe screaming "LONG" won't trigger a trade unless other timeframes agree.
Risk Management
Every trade has:
- Position size: 5-10% of portfolio (never all-in)
- Stop-loss: 3-5% below entry (hard exit)
- Take-profit: 8-15% above entry (target exit)
- Portfolio stop: If total drawdown hits 20%, shut down
class RiskManager:
def __init__(self, portfolio_value: float):
self.portfolio_value = portfolio_value
self.max_position_size = 0.10 # 10% max
self.max_drawdown = 0.20 # 20% portfolio stop
def calculate_position_size(self, signal_strength: float) -> float:
# Scale position size by signal strength (0.5 to 1.0)
base_size = self.portfolio_value * self.max_position_size
return base_size * signal_strength
def should_stop_trading(self, current_value: float) -> bool:
drawdown = (self.portfolio_value - current_value) / self.portfolio_value
return drawdown >= self.max_drawdown
Conservative sizing is why we're at 100% win rate. When a trade goes wrong, the loss is small. When it goes right, the gain is meaningful.
Trade Execution
Solana transactions are fast (400ms finality) and cheap ($0.0002 per transaction).
Here's how we execute:
async def execute_swap(
token_in: str,
token_out: str,
amount: float,
slippage_bps: int = 50
) -> Transaction:
# Get best route from Jupiter
quote = await jupiter.get_quote(
input_mint=token_in,
output_mint=token_out,
amount=amount,
slippage_bps=slippage_bps
)
# Build and sign transaction
tx = await jupiter.build_swap_tx(quote)
signed_tx = wallet.sign(tx)
# Submit and confirm
signature = await rpc.send_transaction(signed_tx)
await rpc.confirm_transaction(signature)
return signature
No API keys. No KYC. Just a wallet and a connection.
What Went Wrong (And How We Fixed It)
1. Network Failures
Solana's RPC nodes fail. A lot. We added exponential backoff:
async def send_with_retry(tx: Transaction, max_retries: int = 5) -> str:
for attempt in range(max_retries):
try:
return await rpc.send_transaction(tx)
except RPCException as e:
if attempt == max_retries - 1:
raise
wait_time = 2 ** attempt # 1s, 2s, 4s, 8s, 16s
await asyncio.sleep(wait_time)
2. Price Feed Latency
WebSocket feeds sometimes lag by 1-2 seconds. We added a staleness check:
def is_price_stale(timestamp: int) -> bool:
age = time.time() - timestamp
return age > 5 # Reject prices older than 5 seconds
3. Regime Changes
Markets shift. What works in a bull market fails in a bear market. We track regime (bull/bear/sideways) and adjust strategy:
def detect_regime(prices: list[float]) -> Regime:
sma_50 = np.mean(prices[-50:])
sma_200 = np.mean(prices[-200:])
if sma_50 > sma_200 * 1.05:
return Regime.BULL # Bias long
elif sma_50 < sma_200 * 0.95:
return Regime.BEAR # Bias short
else:
return Regime.SIDEWAYS # Stay neutral
Results So Far
- 8 trades executed
- $170+ realized profit
- 100% win rate (8/8 profitable)
- Largest gain: +12% in 4 hours
- Largest loss: None yet (smallest gain was +3%)
This won't last forever. Eventually, we'll hit a losing trade. But the system is built to handle it.
Lessons Learned
- Conservative position sizing saves you — We're profitable because losses are capped at 3-5%, while gains run to 10-15%.
- Vote-based signals reduce noise — Requiring consensus across timeframes prevents whipsaws.
- Automation removes emotion — The bot doesn't panic. It doesn't FOMO. It just executes the plan.
- Monitoring is critical — Discord alerts for every trade, every error, every restart. If something breaks, I know immediately.
The Code
CryptoOracle is not open-source (for now). If enough people ask, I might release a stripped-down version.
But the principles are universal:
- Use vote-based signals to filter noise
- Size positions conservatively
- Automate execution to remove emotion
- Monitor everything
Want a trading bot for your strategy? We build autonomous systems that trade, monitor, and alert. Let's talk →