Trade History Implementation in Mobile Exchange App
Exchange trade history is not just an order list. It's a high-update-rate real-time table, filtering by trading pairs and order types, pagination on thousands of records, correct status display and P&L calculation per trade.
Record Types and Data Structure
Exchange history includes several entities important to separate on UI:
- Orders — what user placed: limit, market, stop. Status: open, filled, partially_filled, cancelled
- Trades/Fills — actual order executions. One order can have multiple fills
- P&L — profit calculation on closed positions
class TradeRecord {
final String tradeId;
final String orderId;
final String symbol; // "BTC/USDT"
final TradeSide side; // buy / sell
final double price; // execution price
final double quantity;
final double fee;
final String feeAsset; // "USDT" or "BNB" (if fee discount)
final DateTime executedAt;
final OrderType orderType; // market, limit, stop_limit
}
P&L for spot trading: (sell_price - avg_buy_price) * quantity - fees. For exchanges not providing P&L via API — compute locally, matching buy and sell trades by FIFO.
Data Loading: REST + WebSocket
Historical data — via exchange REST API. Most exchanges (Binance, OKX, Bybit) have /api/v3/myTrades or equivalent with cursor-based pagination by fromId or startTime:
Future<List<TradeRecord>> fetchTradeHistory({
required String symbol,
int? fromId,
DateTime? startTime,
int limit = 50,
}) async {
final response = await dio.get('/api/v3/myTrades', queryParameters: {
'symbol': symbol.replaceAll('/', ''),
if (fromId != null) 'fromId': fromId,
if (startTime != null) 'startTime': startTime.millisecondsSinceEpoch,
'limit': limit,
'timestamp': DateTime.now().millisecondsSinceEpoch,
'signature': _sign(queryString), // HMAC-SHA256
});
return (response.data as List).map(TradeRecord.fromJson).toList();
}
Real-time updates — WebSocket executionReport (Binance) or orders channel. On trade execution event — add trade to list top and update order status.
UI: Virtualized List
Trade history can have thousands of records. ListView.builder with pagination is the only right approach. Regular ListView with 5000 child widgets causes jank on scroll and OOM on weak devices.
NotificationListener<ScrollNotification>(
onNotification: (notification) {
if (notification is ScrollEndNotification &&
notification.metrics.pixels >= notification.metrics.maxScrollExtent - 200) {
_loadNextPage(); // lazy load approaching end
}
return false;
},
child: ListView.builder(
itemCount: _trades.length + (_hasMore ? 1 : 0),
itemBuilder: (context, index) {
if (index == _trades.length) {
return const Center(child: CircularProgressIndicator());
}
return TradeRow(trade: _trades[index]);
},
),
)
Filtering
Filters by trading pair, side (buy/sell), order type, date. Critically important: date filtering via DateTimeRange picker with presets "today / 7 days / 30 days".
Apply filters on server on load (query parameters), not on client — otherwise with large history you'd download everything and filter locally.
Color Coding and Readability
Exchange standard: buy — green, sell — red. Statuses: filled — main color, cancelled — gray, partially_filled — orange. Execution price — slightly larger than other fields.
For P&L display: green with + for profit, red with - for loss. Zero — neutral color.
Export
Export to CSV button — standard requirement for exchange histories (tax reporting). Generate CSV with fields: Date, Pair, Side, Price, Quantity, Fee, Fee Asset, Total. BOM for correct Excel opening.
Scope of Work
- REST API integration with pagination and request signing support (HMAC-SHA256)
- WebSocket for real-time new trade updates
- Virtualized list with lazy loading
- Filters by pair, side, date
- P&L display (if API provides or FIFO calculation)
- CSV export
Timeframe
Basic history with pagination and filters: 3–5 days. With real-time, P&L calculation and export: 1–2 weeks. Cost calculated individually.







