DeFi Trading Bot Architecture: Lessons Learned

Building a production-grade market making toolkit for Cardano native tokens across 4 centralized exchanges.
Why Existing Tools Fail
Every Cardano project with a native token faces the same problem: liquidity. You launch your token, get it listed on a couple of exchanges, and watch as the spread sits at 15% because nobody's making markets. Professional market makers charge $50K/month minimums and require you to hand over API keys to a black box.
The existing alternatives aren't much better. Expensive SaaS platforms offer no visibility into what they're doing with your funds. Python scripts from GitHub are single-exchange with no error handling — one bad WebSocket disconnect from draining your account. CCXT wrappers claim unified APIs but you still need to handle every exchange's quirks around authentication, rate limits, symbol formats, and order precision.
None of these worked for small-to-mid Cardano projects. We needed something multi-exchange from day one, Cardano-native with DEX price aggregation, strategy-agnostic, and fully open source. So we built OpenMM — a TypeScript SDK that unifies MEXC, Bitget, Gate.io, and Kraken behind a single interface, published as an npm package anyone can install.
The Layered Design Philosophy
When your code moves real money, boring architecture is a feature. OpenMM follows a strict layered plugin pattern where each layer has exactly one job and never reaches into layers it shouldn't know about.
The CLI layer handles user interaction through Commander.js — parsing commands, loading configuration, and wiring everything together. It's thin by design.
The strategy layer contains the trading logic. Grid trading is the first strategy, but the layer is designed for extensibility. Strategies receive normalized market data and emit order intents. They never touch exchange-specific code.
Core services sit in the middle — risk management and price aggregation. The risk manager acts as a gate between strategy decisions and order execution. The price aggregation service computes a confidence-weighted price from multiple Cardano DEX pools via the Iris API and four CEX sources.
The exchange abstraction is where the real architectural investment lives. Every exchange connector extends a base class with ~20 methods covering REST APIs and WebSocket streams. A dedicated data mapper translates each exchange's raw response formats into OpenMM's canonical types. The strategy layer works with Order, Ticker, and OrderBook — always the same shape, regardless of which exchange produced them.
Exchange implementations are self-contained modules. Each one has a connector, data mapper, WebSocket handler, and authentication layer. Adding a new exchange means implementing these components — nothing else changes.
The Decisions That Mattered Most
Unified Exchange Abstraction
The problem this solves is deceptively complex. MEXC sends protobuf-encoded WebSocket messages where the docs say JSON. Gate.io uses string-typed numbers everywhere. Kraken nests data under cryptic keys. Bitget requires a passphrase on top of key/secret. Symbol formats differ: INDYUSDT vs INDY_USDT vs ADAUSD.
Rather than papering over these differences in business logic, we centralized all normalization in a generic data mapper. Each exchange defines its own raw types, and the mapper translates them to canonical types. This means exchange-specific parsing is isolated and independently testable.
When we added the fourth exchange (Kraken), it took roughly a day. Without this abstraction, each exchange would have been a weeks-long integration.
The Data Mapper Pattern
This was our most impactful decision. Every piece of data entering the system gets normalized. Every API call converts outbound. We also baked defensive parsing helpers into the base class — parseTimestamp, parsePrice, parseAmount — because every exchange has its own opinions about whether numbers should be strings, integers, or floating-point, and whether timestamps are milliseconds, seconds, or ISO strings.
abstract class BaseExchangeDataMapper<TRawOrder, TRawBalance, TRawTicker, ...> { abstract mapOrder(exchangeOrder: TRawOrder): Order; abstract mapTicker(exchangeTicker: TRawTicker): Ticker; // ... canonical types out, raw types in }
The type parameters are key. TypeScript catches mapping errors at compile time, and the strict types mean you can't accidentally compare against a status string that doesn't exist.
Strategy Separation
The grid trading strategy places buy orders below the current price and sell orders above it at regular intervals, profiting from natural market oscillation. The interesting complexity is in lifecycle management: order fills trigger grid recreation centered on the new price, price deviation beyond a threshold triggers adjustment, and debouncing prevents cascading recreations when several orders fill rapidly.
A concurrency guard prevents overlapping grid modifications. If an order fill event arrives while the grid is being recreated, it's silently ignored rather than triggering a race condition. Every automated action has a cooldown — this applies to all event-driven systems, not just trading.
Risk Management: Deliberately Simple
In trading systems, complex risk logic is a liability — it's hard to reason about, hard to test, and easy to introduce bugs that let bad trades through. Our risk checks are three clear gates.
Price confidence filtering checks that our price data is trustworthy before any trade. The confidence score comes from liquidity-weighted averaging across multiple DEX pools. If fewer pools are available or liquidity is thin, confidence drops and trading halts automatically. This isn't a bug — it's the system protecting your funds.
Position size limits ensure no single position exceeds a configurable percentage of the portfolio. Default is 80%, meaning 20% of your balance is always untouched.
Safety reserves enforce a percentage that's never allocated to trading. Between position limits and safety reserves, you can't accidentally deploy more than ~64% of your total balance in a single grid.
The price aggregation architecture is itself risk infrastructure. ADA/USDT is sourced from four CEX APIs with fallback — if all four fail, trading halts. Token/ADA prices come from Iris DEX pool data with liquidity-weighted averaging. This two-hop approach means we're not trusting any single source. If one exchange is manipulated, the averaged price dampens the impact.
Testing Trading Systems Is Hard
We have 30 test files covering the codebase. Testing a system that moves money presents unique challenges.
Data mappers are the highest-value test targets. Each exchange mapper has comprehensive tests verifying that raw API responses are correctly transformed — status codes normalized, symbols standardized, numbers parsed correctly.
Strategy logic is tested independently from exchange connectors. We inject mock callback functions rather than mocking entire exchange classes. Passing functions rather than objects made tests dramatically simpler — no complex mocking frameworks, no stub chains.
Integration tests run against real exchange APIs with small order sizes, gated behind a separate test command and run sequentially to avoid rate limits. These catch what unit tests can't: auth signature generation, symbol format acceptance, minimum order value enforcement.
What we don't have yet is a paper trading mode that replays historical data against the strategy. The --dry-run flag simulates setup but doesn't simulate fills. Real backtesting infrastructure is on the roadmap, and it's our biggest gap.
Hard-Won Lessons
Every exchange lies about their API. Documentation says one thing, the API does another. MEXC's WebSocket sends protobuf where the docs say JSON. Gate.io's cancelAllOrders doesn't always cancel all orders. Bitget requires price precision to 6 decimal places but doesn't tell you until you get a cryptic error. Write defensive code. Validate everything. Build fallbacks for every critical operation.
Cancellation is the hardest operation. Placing orders is easy. Cancelling them reliably is surprisingly hard. Bulk cancel endpoints fail silently. Individual cancels race with fills. You can't cancel an order that's already been filled but whose fill notification hasn't arrived yet. Always verify cancellation success, and if bulk cancel fails, fall back to individual cancellation.
Real-time systems are fundamentally different from request/response. WebSocket connections drop. Messages arrive out of order. Reconnection logic with exponential backoff is table stakes. MEXC uses protobuf encoding over WebSocket rather than JSON, requiring a dedicated binary decoder. Each exchange multiplexes user data differently. The abstraction layer must hide all of this from the strategy.
Safety reserves should be non-negotiable. Our first grid implementation could theoretically deploy 100% of your balance. We added the 20% reserve after realizing a rapid series of fills during a price crash could leave you with zero quote currency and no ability to adjust. Always keep a buffer. Markets move faster than your code can react.
Keep dependencies minimal. Our runtime has 5 dependencies: chalk, commander, dotenv, winston, and ws. No CCXT, no axios, no lodash. We use Node's built-in fetch for HTTP. Every dependency in a financial system is a trust decision — fewer dependencies means fewer supply chain risks and a smaller attack surface.
TypeScript was the right choice. The generic data mapper types catch mapping errors at compile time. The strict union types mean you can't accidentally compare against a status string that doesn't exist. For systems that handle money, strong typing isn't overhead — it's insurance.
Where It Stands
OpenMM is live, open source on GitHub, and published on npm. It's been used to provide liquidity for Cardano native tokens across MEXC, Bitget, Gate.io, and Kraken. Built under Project Catalyst Fund 14 funding with 22 merged PRs across 3 milestones.
The architecture is intentionally straightforward: abstract the exchanges, normalize the data, keep the strategy logic clean, and fail safe. There's no machine learning, no secret sauce — just well-structured TypeScript that does exactly what it says.
Related Reading
- What Is Market Making? The Engine Behind Every Trade — The fundamentals of what OpenMM automates
- OpenMM vs Hummingbot: Which Crypto Trading Tool? — How OpenMM compares to the most well-known alternative
- What is an MCP Server? Guide to AI Crypto Trading — How OpenMM MCP gives AI agents trading capabilities
- Building High-Performance Cardano Indexers — The data infrastructure that powers our DeFi integrations
Built by QBT Labs — we build open-source infrastructure for the Cardano ecosystem. If you're working on DeFi tooling, trading systems, or blockchain infrastructure, reach out.
Related Articles
How to Choose a Crypto Market Maker in 2026: What Token Projects Actually Need
Choosing a crypto market maker in 2026 is one of the most consequential vendor decisions a post-TGE project makes. This guide gives you a clear framework for evaluation.
Why Token Projects Lose Money with the Wrong Market Maker
Most token projects think about market making wrong. Loan dumping, fake volume, and wide spreads cost more than the monthly fee. Here's how to avoid it.
Inside Our Market Making Stack: From Signal to Execution
Most market making systems are black boxes. This post opens ours — from the first tick to the filled trade.