Back to all posts
EngineeringSolanaTrading

Building CryptoOracle: Lessons from Shipping a Real Autonomous Trading Bot

·12 min read

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:

  1. Generate signals based on technical analysis
  2. Execute trades automatically on Solana (via Jupiter DEX)
  3. Manage stop-loss and take-profit levels
  4. Alert me via Discord for every action
  5. 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:

  1. Position size: 5-10% of portfolio (never all-in)
  2. Stop-loss: 3-5% below entry (hard exit)
  3. Take-profit: 8-15% above entry (target exit)
  4. 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

  1. Conservative position sizing saves you — We're profitable because losses are capped at 3-5%, while gains run to 10-15%.
  2. Vote-based signals reduce noise — Requiring consensus across timeframes prevents whipsaws.
  3. Automation removes emotion — The bot doesn't panic. It doesn't FOMO. It just executes the plan.
  4. 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 →