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.
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
Paper Trading: Run your bot with simulated trades first
Small Position Testing: Start with 1-5% of capital per trade
Monitor Performance: Track all trades and metrics for 2+ weeks
Gradual Scaling: Increase position sizes as confidence builds
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.