Building Your First Crypto Trading Bot

Learn how to build a fully functional cryptocurrency trading bot powered by Smart Money API. This comprehensive tutorial covers signal detection, trade execution strategy, portfolio management, risk controls, backtesting, and deployment best practices. By the end, you'll have a production-ready bot that trades on real whale movements and market signals.

Bot Architecture Overview

A successful trading bot consists of several interconnected components working together. Understanding this architecture helps you build reliable, maintainable bots.

Key Components

  • Data Ingestion: Receives signals from Smart Money API
  • Signal Processing: Analyzes data and generates trading signals
  • Trade Logic: Determines when and what to trade
  • Execution Engine: Places orders on exchanges
  • Portfolio Manager: Tracks positions and balances
  • Risk Manager: Enforces position limits and stop losses
  • Logging & Monitoring: Tracks performance and issues

Signal Detection Strategy

Build signal detection logic that combines multiple Smart Money API data sources into high-confidence trading signals.

PYTHON
import logging from typing import Dict, Tuple, Optional from enum import Enum class SignalType(Enum): STRONG_BUY = 1.0 BUY = 0.75 NEUTRAL = 0.5 SELL = 0.25 STRONG_SELL = 0.0 class SignalDetector: def __init__(self, api_client): self.api = api_client self.logger = logging.getLogger('SignalDetector') self.min_confirmation = 0.70 # 70% confidence threshold def detect_signals(self, symbol: str) -> Tuple[SignalType, Dict]: """ Detect trading signals by combining multiple data sources """ try: # Fetch all relevant data whales = self._get_whale_signals(symbol) derivatives = self._get_derivatives_signals(symbol) onchain = self._get_onchain_signals(symbol) confirmation = self._get_confirmation_score(symbol) # Combine signals with weighted scoring combined_score = ( whales['score'] * 0.35 + derivatives['score'] * 0.35 + onchain['score'] * 0.20 + confirmation['score'] * 0.10 ) # Determine signal type if combined_score >= 0.80: signal = SignalType.STRONG_BUY elif combined_score >= 0.65: signal = SignalType.BUY elif combined_score >= 0.35: signal = SignalType.NEUTRAL elif combined_score >= 0.20: signal = SignalType.SELL else: signal = SignalType.STRONG_SELL return signal, { 'score': combined_score, 'whale_score': whales['score'], 'derivatives_score': derivatives['score'], 'onchain_score': onchain['score'], 'confirmation_score': confirmation['score'], 'reasoning': self._explain_signal(signal, combined_score) } except Exception as e: self.logger.error(f"Error detecting signals for {symbol}: {e}") return SignalType.NEUTRAL, {'error': str(e)} def _get_whale_signals(self, symbol: str) -> Dict: """Analyze whale positioning""" whales = self.api.get_whales(limit=100) # Count whale positions in this symbol long_count = sum(1 for w in whales['data'] if w['largest_position']['asset'] == symbol and w['leverage'] > 1) short_count = sum(1 for w in whales['data'] if w['position_type'] == 'short') whale_score = (long_count - short_count) / len(whales['data']) whale_score = max(0, min(1, whale_score / 2 + 0.5)) # Normalize to 0-1 return {'score': whale_score, 'long_count': long_count, 'short_count': short_count} def _get_derivatives_signals(self, symbol: str) -> Dict: """Analyze derivatives market conditions""" derivatives = self.api.get_derivatives(symbol) # Analyze funding rate funding_rate = float(derivatives['funding_rate']) if funding_rate > 0.005: # High positive funding funding_score = 0.2 # Bearish (longs paying shorts) elif funding_rate < -0.005: # High negative funding funding_score = 0.8 # Bullish (shorts paying longs) else: funding_score = 0.5 # Analyze open interest trend oi_change = derivatives['oi_24h_change'] if oi_change > 0.15: # Large increase in OI oi_score = 0.3 elif oi_change < -0.15: # Large decrease in OI oi_score = 0.7 else: oi_score = 0.5 combined = (funding_score * 0.6 + oi_score * 0.4) return {'score': combined, 'funding_rate': funding_rate, 'oi_change': oi_change} def _get_onchain_signals(self, symbol: str) -> Dict: """Analyze on-chain flows""" flows = self.api.get_onchain_flows() # Analyze exchange flows (deposits indicate selling pressure) net_flow = flows['total_inflows'] - flows['total_outflows'] if net_flow > 0: # More depositing flow_score = 0.3 # Bearish else: flow_score = 0.7 # Bullish return {'score': flow_score, 'net_flow': net_flow} def _get_confirmation_score(self, symbol: str) -> Dict: """Get confirmation score from API""" signals = self.api.get_signals() for signal in signals['data']: if signal['symbol'] == symbol: return { 'score': signal['confirmation_score'], 'signal_type': signal['signal_type'] } return {'score': 0.5} # Default neutral def _explain_signal(self, signal: SignalType, score: float) -> str: """Generate human-readable explanation""" if signal == SignalType.STRONG_BUY: return f"Strong buy signal ({score:.1%}): Multiple bullish indicators aligned" elif signal == SignalType.BUY: return f"Buy signal ({score:.1%}): Mixed bullish indicators" elif signal == SignalType.NEUTRAL: return f"Neutral signal ({score:.1%}): No clear direction" elif signal == SignalType.SELL: return f"Sell signal ({score:.1%}): Mixed bearish indicators" else: return f"Strong sell signal ({score:.1%}): Multiple bearish indicators aligned"

Trade Execution Framework

Implement robust trade execution with proper order management and exchange integration.

PYTHON
from dataclasses import dataclass from enum import Enum from datetime import datetime class OrderStatus(Enum): PENDING = 'pending' FILLED = 'filled' PARTIAL = 'partial' CANCELLED = 'cancelled' FAILED = 'failed' @dataclass class Trade: id: str symbol: str side: str # 'buy' or 'sell' size: float entry_price: float entry_time: datetime stop_loss: Optional[float] = None take_profit: Optional[float] = None status: OrderStatus = OrderStatus.PENDING exit_price: Optional[float] = None exit_time: Optional[datetime] = None class TradeExecutor: def __init__(self, exchange_client, portfolio_manager, risk_manager): self.exchange = exchange_client self.portfolio = portfolio_manager self.risk = risk_manager self.logger = logging.getLogger('TradeExecutor') self.open_trades = {} def execute_signal(self, signal: SignalType, symbol: str, signal_data: Dict) -> Optional[Trade]: """Execute a trade based on signal""" # Validate signal strength if signal_data['score'] < 0.65: self.logger.info(f"Signal score {signal_data['score']:.2%} below threshold") return None # Check if we should trade if not self._should_trade(symbol, signal): return None # Calculate position size size = self.risk.calculate_position_size(symbol, signal) if size <= 0: self.logger.warning(f"Position size calculation returned {size}") return None try: # Determine trade direction if signal in [SignalType.STRONG_BUY, SignalType.BUY]: side = 'buy' else: side = 'sell' # Get current price current_price = self._get_current_price(symbol) # Calculate stop loss and take profit stop_loss = self.risk.calculate_stop_loss(symbol, side, current_price) take_profit = self.risk.calculate_take_profit(symbol, side, current_price) # Place order order = self.exchange.place_order( symbol=symbol, side=side, size=size, price=current_price, stop_loss=stop_loss, take_profit=take_profit ) # Create trade record trade = Trade( id=order['id'], symbol=symbol, side=side, size=size, entry_price=current_price, entry_time=datetime.now(), stop_loss=stop_loss, take_profit=take_profit ) self.open_trades[trade.id] = trade self.logger.info(f"Trade {trade.id}: {side} {size} {symbol} @ {current_price}") return trade except Exception as e: self.logger.error(f"Trade execution failed: {e}") return None def _should_trade(self, symbol: str, signal: SignalType) -> bool: """Check if we should trade this signal""" # Check if we already have position in this symbol if self.portfolio.has_open_position(symbol): return False # Check if we have available capital if not self.portfolio.has_available_capital(): return False return True def _get_current_price(self, symbol: str) -> float: """Get current price from exchange""" return self.exchange.get_latest_price(symbol) def check_exit_conditions(self): """Periodically check if trades should be closed""" for trade_id, trade in list(self.open_trades.items()): current_price = self._get_current_price(trade.symbol) # Check stop loss if trade.stop_loss and self._check_stop_loss(trade, current_price): self.close_trade(trade_id, current_price, 'stop_loss') # Check take profit elif trade.take_profit and self._check_take_profit(trade, current_price): self.close_trade(trade_id, current_price, 'take_profit') def close_trade(self, trade_id: str, exit_price: float, reason: str): """Close an open trade""" trade = self.open_trades.get(trade_id) if not trade: return try: self.exchange.close_position(trade.id) trade.exit_price = exit_price trade.exit_time = datetime.now() trade.status = OrderStatus.FILLED profit = (exit_price - trade.entry_price) * trade.size profit_pct = (exit_price - trade.entry_price) / trade.entry_price self.logger.info( f"Trade {trade_id} closed ({reason}): " f"${profit:.2f} ({profit_pct:.2%})" ) del self.open_trades[trade_id] except Exception as e: self.logger.error(f"Failed to close trade {trade_id}: {e}")

Portfolio Management

Track positions, calculate returns, and manage capital allocation across multiple trades.

PYTHON
from collections import defaultdict class PortfolioManager: def __init__(self, initial_capital: float, max_position_size: float = 0.1): self.initial_capital = initial_capital self.current_capital = initial_capital self.max_position_size = max_position_size # Max 10% per position self.positions = defaultdict(float) # Symbol -> size self.trade_history = [] self.logger = logging.getLogger('Portfolio') def add_position(self, symbol: str, size: float, price: float): """Add a position to portfolio""" cost = size * price if cost > self.current_capital: raise ValueError(f"Insufficient capital. Need ${cost}, have ${self.current_capital}") self.positions[symbol] += size self.current_capital -= cost self.logger.info(f"Added {size} {symbol} @ ${price}") def close_position(self, symbol: str, size: float, price: float) -> float: """Close a position and return profit/loss""" if self.positions[symbol] < size: raise ValueError(f"Insufficient {symbol} to close") revenue = size * price self.positions[symbol] -= size self.current_capital += revenue return revenue def get_portfolio_value(self, current_prices: Dict[str, float]) -> float: """Calculate total portfolio value""" holdings_value = sum( self.positions[symbol] * current_prices.get(symbol, 0) for symbol in self.positions ) return self.current_capital + holdings_value def get_returns(self, current_prices: Dict[str, float]) -> Tuple[float, float]: """Get absolute and percentage returns""" portfolio_value = self.get_portfolio_value(current_prices) absolute_return = portfolio_value - self.initial_capital percentage_return = absolute_return / self.initial_capital return absolute_return, percentage_return def has_available_capital(self) -> bool: """Check if we have capital for new trades""" return self.current_capital > 0 def has_open_position(self, symbol: str) -> bool: """Check if we have position in symbol""" return self.positions[symbol] > 0 def get_position_percentage(self, symbol: str, current_price: float) -> float: """Get position size as % of portfolio""" position_value = self.positions[symbol] * current_price portfolio_value = sum( self.positions[s] * current_price for s in self.positions ) if portfolio_value == 0: return 0 return position_value / portfolio_value

Risk Management Controls

Implement critical risk controls to protect your capital and limit losses.

PYTHON
class RiskManager: def __init__( self, max_daily_loss: float = 0.05, # 5% of capital max_position_size: float = 0.10, # 10% per position risk_per_trade: float = 0.02 # 2% risk per trade ): self.max_daily_loss = max_daily_loss self.max_position_size = max_position_size self.risk_per_trade = risk_per_trade self.daily_loss = 0 self.logger = logging.getLogger('RiskManager') def calculate_position_size( self, symbol: str, signal: SignalType, current_price: float, stop_loss_pct: float = 0.02 ) -> float: """Calculate position size based on risk""" # Scale position size by signal strength signal_multiplier = float(signal.value) # Risk per trade in dollars risk_amount = self.portfolio.initial_capital * self.risk_per_trade # Calculate position size stop_loss_distance = current_price * stop_loss_pct position_size = (risk_amount / stop_loss_distance) * signal_multiplier # Check against max position size max_size = self.portfolio.initial_capital * self.max_position_size / current_price position_size = min(position_size, max_size) return position_size def calculate_stop_loss( self, symbol: str, side: str, current_price: float, atr: Optional[float] = None ) -> float: """Calculate stop loss price""" # Use ATR if available, otherwise use fixed percentage if atr: stop_loss_distance = atr * 2 else: stop_loss_distance = current_price * 0.02 # 2% if side == 'buy': return current_price - stop_loss_distance else: return current_price + stop_loss_distance def calculate_take_profit( self, symbol: str, side: str, current_price: float, risk_reward_ratio: float = 2.0 ) -> float: """Calculate take profit price""" stop_loss_distance = current_price * 0.02 take_profit_distance = stop_loss_distance * risk_reward_ratio if side == 'buy': return current_price + take_profit_distance else: return current_price - take_profit_distance def check_daily_loss_limit(self, current_loss: float) -> bool: """Check if daily loss exceeds limit""" self.daily_loss = current_loss max_loss = self.portfolio.initial_capital * self.max_daily_loss if current_loss > max_loss: self.logger.warning(f"Daily loss limit exceeded: ${current_loss:.2f}") return False return True def validate_trade(self, symbol: str, size: float, price: float) -> bool: """Validate trade before execution""" # Check position size limit position_value = size * price max_position_value = self.portfolio.initial_capital * self.max_position_size if position_value > max_position_value: self.logger.warning(f"Position size exceeds limit") return False # Check available capital if position_value > self.portfolio.current_capital: self.logger.warning(f"Insufficient capital") return False return True

Backtesting Your Strategy

Backtest your bot on historical data before trading live.

PYTHON
class BacktestEngine: def __init__(self, api_client, initial_capital: float = 10000): self.api = api_client self.initial_capital = initial_capital self.current_capital = initial_capital self.trades = [] self.equity_curve = [] self.logger = logging.getLogger('Backtest') def run_backtest(self, symbol: str, days: int = 30): """Run backtest on historical data""" # Fetch historical data historical_data = self.api.get_historical_data( symbol=symbol, days=days, resolution='1h' # Hourly candles ) signal_detector = SignalDetector(self.api) portfolio = PortfolioManager(self.initial_capital) risk = RiskManager() results = { 'trades': [], 'total_return': 0, 'win_rate': 0, 'max_drawdown': 0, 'sharpe_ratio': 0 } for candle in historical_data: timestamp = candle['timestamp'] price = candle['close'] # Check if we should enter signal, signal_data = signal_detector.detect_signals(symbol) if signal in [SignalType.STRONG_BUY, SignalType.BUY]: size = risk.calculate_position_size(symbol, signal, price) if size > 0: portfolio.add_position(symbol, size, price) results['trades'].append({ 'entry_time': timestamp, 'entry_price': price, 'side': 'buy' }) # Check if we should exit if portfolio.has_open_position(symbol): if signal in [SignalType.STRONG_SELL, SignalType.SELL]: size = portfolio.positions[symbol] revenue = portfolio.close_position(symbol, size, price) results['trades'][-1].update({ 'exit_time': timestamp, 'exit_price': price, 'profit': revenue - (results['trades'][-1]['entry_price'] * size) }) # Record equity portfolio_value = portfolio.get_portfolio_value({symbol: price}) self.equity_curve.append(portfolio_value) # Calculate metrics results['total_return'] = (portfolio_value - self.initial_capital) / self.initial_capital results['win_rate'] = self._calculate_win_rate(results['trades']) results['max_drawdown'] = self._calculate_max_drawdown() results['sharpe_ratio'] = self._calculate_sharpe_ratio() self.logger.info(f"Backtest Results: {results}") return results def _calculate_win_rate(self, trades: list) -> float: """Calculate percentage of winning trades""" if not trades: return 0 wins = sum(1 for t in trades if t.get('profit', 0) > 0) return wins / len(trades) def _calculate_max_drawdown(self) -> float: """Calculate maximum drawdown""" if not self.equity_curve: return 0 peak = self.equity_curve[0] max_drawdown = 0 for value in self.equity_curve: if value > peak: peak = value drawdown = (peak - value) / peak max_drawdown = max(max_drawdown, drawdown) return max_drawdown def _calculate_sharpe_ratio(self, risk_free_rate: float = 0.02) -> float: """Calculate Sharpe ratio""" if len(self.equity_curve) < 2: return 0 returns = [(self.equity_curve[i] - self.equity_curve[i-1]) / self.equity_curve[i-1] for i in range(1, len(self.equity_curve))] avg_return = sum(returns) / len(returns) variance = sum((r - avg_return) ** 2 for r in returns) / len(returns) std_dev = variance ** 0.5 if std_dev == 0: return 0 return (avg_return - risk_free_rate) / std_dev

Moving to Live Trading

When ready to trade live, follow a careful progression from small positions to full scale.

Live Trading Progression

  1. Paper Trading: Run your bot with simulated trades first
  2. Small Position Testing: Start with 1-5% of capital per trade
  3. Monitor Performance: Track all trades and metrics for 2+ weeks
  4. Gradual Scaling: Increase position sizes as confidence builds
  5. Full Scale: Deploy with full position sizing after proven performance

Monitoring and Alerts

Set up comprehensive monitoring to track bot health, performance, and detect issues.

PYTHON
class BotMonitor: def __init__(self, bot, alert_email: str): self.bot = bot self.alert_email = alert_email self.logger = logging.getLogger('Monitor') def check_bot_health(self): """Check if bot is running correctly""" checks = { 'api_connection': self._check_api(), 'exchange_connection': self._check_exchange(), 'balance_sufficient': self._check_balance(), 'no_errors': self._check_errors() } if not all(checks.values()): self._send_alert(f"Bot health check failed: {checks}") return checks def _check_api(self) -> bool: """Check API connectivity""" try: self.bot.api.get_market_status() return True except: return False def _check_exchange(self) -> bool: """Check exchange connectivity""" try: self.bot.exchange.get_account_balance() return True except: return False def _check_balance(self) -> bool: """Check if minimum balance maintained""" balance = self.bot.exchange.get_account_balance() return balance > self.bot.initial_capital * 0.5 def _check_errors(self) -> bool: """Check for recent errors""" recent_errors = [e for e in self.bot.error_log if ( datetime.now() - e['timestamp']).total_seconds() < 3600 ] return len(recent_errors) < 5 def generate_daily_report(self) -> str: """Generate daily performance report""" trades_today = [t for t in self.bot.trade_history if ( datetime.now() - t['timestamp']).days == 0 ] report = f""" Daily Bot Report - {datetime.now().strftime('%Y-%m-%d')} Trades Executed: {len(trades_today)} Win Rate: {self._calculate_win_rate(trades_today):.1%} Daily P&L: ${sum(t.get('profit', 0) for t in trades_today):.2f} Current Portfolio Value: ${self.bot.portfolio.get_portfolio_value({})} Active Positions: {len([p for p in self.bot.portfolio.positions.values() if p > 0])} Risk Level: {self._assess_risk_level()} """ return report def _send_alert(self, message: str): """Send alert via email""" # Implementation depends on your email provider pass
Critical Reminder: Always backtest thoroughly before live trading. Start with small positions and carefully monitor results. Trading bots carry significant risks including rapid losses, technical failures, and market gaps. Never deploy without proper risk management and stop losses.

For more advanced strategies, explore our guides on confirmation scores, derivatives analysis, and risk management systems.

Build Your Trading Bot Today

Access whale signals, funding rates, and on-chain flows needed to build winning trading strategies. Start with the Trader plan for 200 daily requests.

Start Building Bots