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 CCXTAuthenticates correctly with API keysSubscribes to real-time price and order updatesHandles orders with proper error handlingTracks positions, P&L, and risk metricsSurvives reconnections and unexpected errorsStrategy 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