← Back to Blog
TUTORIALS

Building a Crypto Perpetuals Trading Bot in Python: Complete Guide

May 29, 2026 · 10 min read · LMEX.AI

A trading bot for crypto perpetuals is fundamentally different from a stock trading bot. Perpetuals never close, funding rates are continuous, leverage is built-in, and the market structure produces unique opportunities and risks. This article walks through building a working perpetuals bot for LMEX in Python — not a strategy guide, but the infrastructure that any strategy runs on top of.


What we're building


A reusable framework that:


  • Connects to LMEX via the official Python SDK or CCXT
  • Authenticates correctly with API keys
  • Subscribes to real-time price and order updates
  • Handles orders with proper error handling
  • Tracks positions, P&L, and risk metrics
  • Survives reconnections and unexpected errors

  • Strategy logic plugs in on top. This article focuses on the framework.


    Project setup


    Standard Python project structure:


    trading-bot/
    ├── config.py          # API keys, settings (gitignored)
    ├── exchange.py        # Exchange connection wrapper
    ├── strategy.py        # Your trading logic
    ├── risk.py            # Position sizing and limits
    ├── state.py           # Persistent state
    ├── main.py            # Entry point
    └── requirements.txt

    Dependencies:


    ccxt==4.4.0
    pandas==2.2.0
    websocket-client==1.7.0
    python-dotenv==1.0.0

    Use a `.env` file for secrets:


    LMEX_API_KEY=your_key_here
    LMEX_API_SECRET=your_secret_here
    LMEX_TESTNET=true

    Never commit secrets. Use environment variables loaded via `python-dotenv`.


    Exchange wrapper


    The exchange interface should hide CCXT details and provide methods specific to your bot's needs:


    import ccxt
    import os
    from dotenv import load_dotenv
    
    load_dotenv()
    
    class LMEXClient:
        def __init__(self):
            self.exchange = ccxt.lmex({
                'apiKey': os.getenv('LMEX_API_KEY'),
                'secret': os.getenv('LMEX_API_SECRET'),
                'enableRateLimit': True,
                'options': {
                    'defaultType': 'future',
                    'testnet': os.getenv('LMEX_TESTNET') == 'true',
                },
            })
        
        def get_balance(self):
            balance = self.exchange.fetch_balance()
            return balance['USDT']['free']
        
        def get_position(self, symbol):
            positions = self.exchange.fetch_positions([symbol])
            for p in positions:
                if p['symbol'] == symbol and p['contracts'] > 0:
                    return p
            return None
        
        def get_orderbook(self, symbol, limit=20):
            return self.exchange.fetch_order_book(symbol, limit=limit)
        
        def place_limit_order(self, symbol, side, qty, price, post_only=True):
            params = {'postOnly': post_only}
            return self.exchange.create_order(symbol, 'limit', side, qty, price, params)
        
        def place_market_order(self, symbol, side, qty):
            return self.exchange.create_order(symbol, 'market', side, qty)
        
        def cancel_order(self, order_id, symbol):
            return self.exchange.cancel_order(order_id, symbol)
        
        def cancel_all_orders(self, symbol):
            return self.exchange.cancel_all_orders(symbol)

    This is the bot's interface to the exchange. Strategies don't need to know about CCXT specifics.


    A simple strategy


    Here's a minimum viable strategy — a mean-reversion bot that buys when price drops below 1-hour low and sells when above 1-hour high:


    class MeanReversionStrategy:
        def __init__(self, client: LMEXClient, symbol: str, qty: float):
            self.client = client
            self.symbol = symbol
            self.qty = qty
            self.position_side = None
        
        def get_recent_prices(self):
            candles = self.client.exchange.fetch_ohlcv(self.symbol, '1h', limit=24)
            return [c[4] for c in candles]  # close prices
        
        def should_enter_long(self, current_price, prices):
            recent_low = min(prices[-24:-1])  # exclude current
            return current_price < recent_low * 0.995  # 0.5% below 24h low
        
        def should_enter_short(self, current_price, prices):
            recent_high = max(prices[-24:-1])
            return current_price > recent_high * 1.005  # 0.5% above 24h high
        
        def should_exit(self, current_price, prices):
            if self.position_side is None:
                return False
            mean_price = sum(prices) / len(prices)
            # Exit when price is close to recent mean
            if self.position_side == 'long' and current_price > mean_price:
                return True
            if self.position_side == 'short' and current_price < mean_price:
                return True
            return False
        
        def step(self):
            prices = self.get_recent_prices()
            current_price = prices[-1]
            
            if self.position_side is None:
                if self.should_enter_long(current_price, prices):
                    self.client.place_market_order(self.symbol, 'buy', self.qty)
                    self.position_side = 'long'
                elif self.should_enter_short(current_price, prices):
                    self.client.place_market_order(self.symbol, 'sell', self.qty)
                    self.position_side = 'short'
            else:
                if self.should_exit(current_price, prices):
                    side = 'sell' if self.position_side == 'long' else 'buy'
                    self.client.place_market_order(self.symbol, side, self.qty)
                    self.position_side = None

    This is intentionally simple. The point is showing how strategy logic plugs into the framework.


    Risk management


    Before any order is placed, run it through risk checks:


    class RiskManager:
        def __init__(self, client: LMEXClient, max_position_value=1000, daily_loss_limit_pct=0.05):
            self.client = client
            self.max_position_value = max_position_value
            self.daily_loss_limit_pct = daily_loss_limit_pct
            self.day_start_balance = client.get_balance()
        
        def can_open_position(self, symbol, qty, price):
            # Position value check
            if qty * price > self.max_position_value:
                return False, "Exceeds max position value"
            
            # Daily loss check
            current_balance = self.client.get_balance()
            loss_pct = (current_balance - self.day_start_balance) / self.day_start_balance
            if loss_pct < -self.daily_loss_limit_pct:
                return False, "Daily loss limit hit"
            
            return True, "OK"

    Every order goes through `risk_mgr.can_open_position()` before reaching the exchange. The bot literally cannot place orders that violate the risk rules.


    Main loop


    Put it together:


    import time
    import logging
    
    logging.basicConfig(level=logging.INFO, format='%(asctime)s %(levelname)s %(message)s')
    
    def main():
        client = LMEXClient()
        risk_mgr = RiskManager(client)
        strategy = MeanReversionStrategy(client, 'BTC-PERP', 0.01)
        
        logging.info("Starting bot")
        
        while True:
            try:
                strategy.step()
                time.sleep(60)  # Check every minute
            except KeyboardInterrupt:
                logging.info("Shutting down")
                break
            except Exception as e:
                logging.error(f"Error: {e}")
                time.sleep(30)  # Brief pause before retry
    
    if __name__ == '__main__':
        main()

    This is the absolute bones. Real bots add WebSocket subscriptions, state persistence, monitoring, and dozens of other features. But this skeleton actually runs and trades.


    Common mistakes to avoid


    **Hard-coded API keys.** Use environment variables. Always.


    **Not handling exchange API failures.** The exchange will return errors. Plan for it. Don't crash on a single API timeout.


    **No paper trading.** Run on testnet for at least 2 weeks before any real money. Catch the obvious bugs while losing nothing.


    **Optimistic position size.** Start with the smallest allowed order. Scale up only after weeks of stable operation.


    **No monitoring.** A bot you can't see is a bot you don't really have. Set up basic alerts (email, Slack, Telegram) for: orders placed, errors, daily P&L summary.


    Frequently Asked Questions


    Q: Should I use CCXT or the LMEX native SDK?

    For most retail bots, CCXT is fine and easier to learn. The native SDK gives slightly better performance and feature coverage. Start with CCXT.


    Q: How do I handle reconnections?

    For REST APIs, just retry with exponential backoff. For WebSocket, implement reconnect logic with resubscription. Most WebSocket libraries don't do this automatically.


    Q: How do I test without real money?

    LMEX testnet. Set LMEX_TESTNET=true in your config. Use testnet API keys and testnet endpoints. Behavior matches production closely.


    Q: What's the minimum capital to run a perpetuals bot?

    \$1,000-2,000 for testing and learning. \$10,000+ before serious strategies become viable. Smaller accounts make fees a higher percentage of returns.


    Related Articles


    → LMEX API Python Tutorial: Connect, Authenticate, Place Your First Order
    → Backtesting Your LMEX Trading Bot in Python: A Practical Guide
    → WebSocket vs REST for Trading Bots: When Each One Wins
    ← All ArticlesBuild a Bot →