Skip to main content

Overview

The Interactive Brokers integration allows you to upload PDF statements and automatically extract trade data using AI-powered OCR and parsing. This is ideal for importing historical trades without requiring API access.
Supports Flex Query statements and standard trade confirmations from Interactive Brokers.

Features

  • PDF upload and text extraction
  • AI-powered order detection
  • Automatic instrument information parsing
  • FIFO trade matching (First-In-First-Out)
  • Commission and fee calculation
  • Futures contract symbol extraction
  • Multi-page statement support

How It Works

Processing Pipeline

  1. PDF Upload: User uploads IBKR statement PDF
  2. OCR Extraction: Extract text using pdf2json library
  3. Order Parsing: Identify buy/sell orders with regex patterns
  4. Instrument Parsing: Extract contract details (symbol, expiry, multiplier)
  5. FIFO Matching: Match buys with sells using FIFO algorithm
  6. P&L Calculation: Compute profit/loss and commissions
  7. Trade Saving: Store matched trades in Deltalytix format

Setup Instructions

Prerequisites

  • Interactive Brokers account with trade history
  • PDF statement downloaded from IBKR portal
  • Valid PDF file (not password-protected)

Step 1: Upload PDF Statement

Upload your IBKR PDF statement:
const formData = new FormData()
formData.append('file', pdfFile)

const response = await fetch('/api/imports/ibkr/ocr', {
  method: 'POST',
  body: JSON.stringify({
    attachments: [{
      type: 'application/pdf',
      content: await pdfFile.arrayBuffer()
    }]
  })
})

const { text } = await response.json()
console.log('Extracted text:', text)
See app/api/imports/ibkr/ocr/route.ts:52-108

Step 2: Extract Orders and Instruments

Parse the extracted text to identify orders:
const response = await fetch('/api/imports/ibkr/extract-orders', {
  method: 'POST',
  headers: { 'Content-Type': 'application/json' },
  body: JSON.stringify({ text: extractedText })
})

const { orders, instruments } = await response.json()

console.log(`Found ${orders.length} orders`)
console.log(`Identified ${instruments.length} instruments`)
See app/api/imports/ibkr/extract-orders/route.ts:135-175

Step 3: Match Trades with FIFO

Match buy and sell orders to create complete trades:
const response = await fetch('/api/imports/ibkr/fifo-computation', {
  method: 'POST',
  headers: { 'Content-Type': 'application/json' },
  body: JSON.stringify({ 
    orders: orders,
    instruments: instruments 
  })
})

const trades = await response.json()
console.log(`Matched ${trades.length} complete trades`)

API Reference

POST /api/imports/ibkr/ocr

Extract text from IBKR PDF statement. Request Body:
{
  attachments: [{
    type: 'application/pdf'
    content: ArrayBuffer | string  // PDF content or base64
  }]
}
Response:
{
  text: string  // Extracted text from PDF
}
Example:
const pdfBuffer = await file.arrayBuffer()

const response = await fetch('/api/imports/ibkr/ocr', {
  method: 'POST',
  headers: { 'Content-Type': 'application/json' },
  body: JSON.stringify({
    attachments: [{
      type: 'application/pdf',
      content: pdfBuffer
    }]
  })
})

const { text } = await response.json()
See app/api/imports/ibkr/ocr/route.ts:52-108

POST /api/imports/ibkr/extract-orders

Parse orders and instrument information from extracted text. Request Body:
{
  text: string  // Extracted PDF text
}
Response:
{
  orders: Array<{
    rawSymbol: string       // e.g., "MESM5"
    side: 'BUY' | 'SELL'
    quantity: number
    price: number
    timestamp: string       // ISO 8601
    commission: number
    accountNumber: string   // e.g., "U***1234"
    orderId: string        // Generated ID
    orderType: string      // e.g., "O", "C"
  }>
  instruments: Array<{
    symbol: string          // e.g., "MESM5"
    description: string     // e.g., "MES 20JUN25"
    conid: string          // Contract ID
    underlying: string      // e.g., "MES"
    listingExchange: string // e.g., "CME"
    multiplier: number      // Contract size
    expiry: string         // e.g., "2025-06-20"
    deliveryMonth: string  // e.g., "2025-06"
    instrumentType: string // e.g., "Futures"
  }>
}
Example:
const response = await fetch('/api/imports/ibkr/extract-orders', {
  method: 'POST',
  headers: { 'Content-Type': 'application/json' },
  body: JSON.stringify({ text: extractedText })
})

const { orders, instruments } = await response.json()

orders.forEach(order => {
  console.log(`${order.side} ${order.quantity} ${order.rawSymbol} @ $${order.price}`)
})
See app/api/imports/ibkr/extract-orders/route.ts:135-175

POST /api/imports/ibkr/fifo-computation

Match orders using FIFO algorithm and compute trade P&L. Request Body:
{
  orders: Order[]           // From extract-orders endpoint
  instruments: Instrument[] // From extract-orders endpoint
}
Response:
Array<{
  instrument: string       // Symbol (e.g., "MES")
  quantity: number        // Contracts traded
  entryPrice: string      // Entry price
  closePrice: string      // Exit price
  entryDate: string       // ISO 8601
  closeDate: string       // ISO 8601
  entryId: string        // Entry order ID
  closeId: string        // Exit order ID
  pnl: number            // Profit/loss in dollars
  commission: number     // Total commission
  timeInPosition: number // Duration in seconds
  side: 'long' | 'short' // Trade direction
  orderIds: string[]     // All related order IDs
}>

PDF Parsing Details

Text Extraction

The OCR endpoint uses pdf2json for reliable PDF processing:
import PDFParser from 'pdf2json'

const pdfParser = new PDFParser()

pdfParser.on('pdfParser_dataReady', (pdfData) => {
  let extractedText = ''
  
  pdfData.Pages.forEach((page) => {
    page.Texts.forEach((text) => {
      text.R.forEach((run) => {
        extractedText += decodeURIComponent(run.T) + ' '
      })
    })
    extractedText += '\n'
  })
  
  console.log(extractedText)
})

pdfParser.parseBuffer(pdfBuffer)
See app/api/imports/ibkr/ocr/route.ts:6-50

Order Pattern Matching

Orders are extracted using regex patterns:
const orderPattern = /U\*\*\*(\d+)\s+([A-Z0-9]+)\s+(\d{4}-\d{2}-\d{2}),\s*(\d{2}:\d{2}:\d{2})\s+(\d{4}-\d{2}-\d{2})\s+-\s+(BUY|SELL)\s+(-?\d+)\s+([\d,]+\.\d+)\s+(-?[\d,]+\.\d+)\s+(-?[\d,]+\.\d+)\s+([\d,]+\.\d+)\s+([A-Z]+)\s+([OC])/g

let match
while ((match = orderPattern.exec(tradesText)) !== null) {
  const [
    _, accountId, symbol, date, time, settleDate,
    side, quantity, price, value, commission, fee,
    orderType, code
  ] = match
  
  orders.push({
    rawSymbol: symbol,
    side: side as 'BUY' | 'SELL',
    quantity: Math.abs(parseInt(quantity)),
    price: parseFloat(price.replace(/,/g, '')),
    timestamp: `${date}T${time}`,
    commission: parseFloat(fee.replace(/,/g, '')) + 
                parseFloat(commission.replace(/,/g, '')),
    accountNumber: `U***${accountId}`,
    orderId: `${side.charAt(0)}${index}${time.split(':')[2]}`,
    orderType: orderType
  })
}
See app/api/imports/ibkr/extract-orders/route.ts:18-74

Instrument Parsing

Contract details are extracted from the “Financial Instrument Information” section:
const instrumentPattern = /([A-Z0-9]+)\s+([A-Z0-9]+\s+[A-Z0-9]+)\s+(\d+)\s+([A-Z0-9]+)\s+([A-Z]+)\s+(\d+)\s+(\d{4}-\d{2}-\d{2})\s+(\d{4}-\d{2})/g

while ((match = instrumentPattern.exec(dataText)) !== null) {
  const [
    _, symbol, description, conid, underlying,
    exchange, multiplier, expiry, deliveryMonth
  ] = match
  
  instruments.push({
    symbol: symbol.trim(),
    description: description.trim(),
    conid: conid,
    underlying: underlying,
    listingExchange: exchange,
    multiplier: parseInt(multiplier),
    expiry: expiry,
    deliveryMonth: deliveryMonth,
    instrumentType: 'Futures'
  })
}
See app/api/imports/ibkr/extract-orders/route.ts:77-133

FIFO Trade Matching

Trades are matched using First-In-First-Out (FIFO) algorithm:

Algorithm Overview

  1. Group by Symbol: Separate orders by instrument
  2. Sort by Timestamp: Process in chronological order
  3. Match Buy/Sell: Pair earliest unmatched orders
  4. Calculate P&L: Compute profit/loss per matched pair
  5. Handle Partial Fills: Split orders if quantities don’t match

Example

// Input orders
const orders = [
  { side: 'BUY',  quantity: 2, price: 5000, timestamp: '2025-01-01T10:00:00' },
  { side: 'BUY',  quantity: 1, price: 5010, timestamp: '2025-01-01T11:00:00' },
  { side: 'SELL', quantity: 2, price: 5020, timestamp: '2025-01-01T12:00:00' },
  { side: 'SELL', quantity: 1, price: 5030, timestamp: '2025-01-01T13:00:00' },
]

// Output trades (FIFO matched)
const trades = [
  { 
    quantity: 2, 
    entryPrice: '5000', 
    closePrice: '5020',
    pnl: (5020 - 5000) * 2 * multiplier  // Long trade
  },
  {
    quantity: 1,
    entryPrice: '5010',
    closePrice: '5030', 
    pnl: (5030 - 5010) * 1 * multiplier  // Long trade
  }
]

UI Components

The IBKR integration includes React components for the import flow:

pdf-upload.tsx

Handles PDF file upload and validation:
import PdfUpload from '@/app/[locale]/dashboard/components/import/ibkr-pdf/pdf-upload'

<PdfUpload
  onUploadComplete={(extractedText) => {
    console.log('PDF uploaded and processed')
    setExtractedText(extractedText)
  }}
  onError={(error) => {
    console.error('Upload failed:', error)
  }}
/>
See app/[locale]/dashboard/components/import/ibkr-pdf/pdf-upload.tsx

pdf-processing.tsx

Displays extracted trades in a table with real-time parsing:
import PdfProcessing from '@/app/[locale]/dashboard/components/import/ibkr-pdf/pdf-processing'

<PdfProcessing
  extractedText={extractedText}
  userId={currentUserId}
  processedTrades={trades}
  setProcessedTrades={setTrades}
  setError={setError}
  setStep={setStep}
/>
See app/[locale]/dashboard/components/import/ibkr-pdf/pdf-processing.tsx:48-536

Features

  • Real-time order extraction streaming
  • Interactive trade table with sorting
  • Hover tooltips showing order details
  • P&L calculation with commission
  • Summary totals row
  • Progress indicators

Error Handling

Error: Invalid file type. Only PDF files are allowed.Cause: Non-PDF file uploadedSolution: Ensure file has .pdf extension and correct MIME type:
if (file.type !== 'application/pdf') {
  throw new Error('Only PDF files are supported')
}
Error: PDF processing failed: [reason]Causes:
  • Password-protected PDF
  • Corrupted PDF file
  • Unsupported PDF version
Solution:
  • Remove password protection from PDF
  • Download fresh copy from IBKR
  • Try a different statement period
Error: Returns empty orders arrayCauses:
  • Statement doesn’t contain trades section
  • Unexpected PDF format
  • Different IBKR statement type
Solution:
  • Ensure PDF is a trade confirmation or Flex Query
  • Check that “Trades” section exists in PDF
  • Verify statement date range includes trades
Error: Order validation failedCause: Extracted order doesn’t match expected schemaSolution: Check order schema requirements:
const orderSchema = z.object({
  rawSymbol: z.string(),
  side: z.enum(['BUY', 'SELL']),
  quantity: z.number().positive(),
  price: z.number().positive(),
  timestamp: z.string(),  // ISO 8601
  commission: z.number().optional(),
  accountNumber: z.string().optional(),
  orderId: z.string().optional()
})

Best Practices

Statement Quality

Use high-quality PDFs directly from IBKR portal, not scanned copies

Date Ranges

Import smaller date ranges (1-3 months) for better accuracy

Validation

Always review extracted trades before saving to database

Backup Data

Keep original PDF statements as backup records

Limitations

  • Supports futures contracts primarily (options/stocks may require schema updates)
  • Requires standard IBKR statement format
  • Cannot process password-protected PDFs
  • Text extraction depends on PDF quality
  • Regex patterns may need updates for different statement versions

Troubleshooting

Orders Not Matching

  1. Check instrument information:
console.log('Instruments:', instruments)
// Verify symbols match between orders and instruments
  1. Inspect order timestamps:
orders.forEach(order => {
  console.log(`${order.side} ${order.rawSymbol} at ${order.timestamp}`)
})
// Ensure timestamps are valid ISO 8601
  1. Verify FIFO logic:
  • Orders must be sorted by timestamp
  • Buy quantity must match available sell quantity
  • Partial fills create multiple trades

Incorrect P&L Calculation

  1. Check contract multiplier:
const instrument = instruments.find(i => i.symbol === order.rawSymbol)
console.log('Multiplier:', instrument?.multiplier)  // e.g., 5 for MES
  1. Verify commission calculation:
const totalCommission = 
  parseFloat(clearingFee) + 
  parseFloat(exchangeFee) + 
  parseFloat(nfaFee)
  1. Confirm price precision:
const price = parseFloat(priceString.replace(/,/g, ''))
// Remove thousands separator before parsing

Next Steps

View Imported Trades

Review and analyze your IBKR trades

CSV Import

Try CSV import for more control

Build docs developers (and LLMs) love