Skip to main content

Overview

The CSV import feature allows you to import trades from any broker by uploading a CSV file. Deltalytix uses AI to automatically map your CSV columns to the correct trade fields, making imports from any broker quick and accurate.
The AI analyzes both column names AND data patterns to intelligently map fields, even handling duplicate column names.

Features

  • Universal CSV import for any broker
  • AI-powered column mapping with GPT-4
  • Handles duplicate column names intelligently
  • Manual mapping override available
  • Real-time field validation
  • Sample data preview
  • Required vs optional field indicators

Supported Trade Fields

Required Fields

instrument
string
required
Trading symbol or ticker (e.g., ES, NQ, EURUSD, BTCUSD)
quantity
number
required
Number of contracts or units traded
entryPrice
number
required
Buy/entry price for the trade
closePrice
number
required
Sell/exit price for the trade
entryDate
datetime
required
Entry/buy timestamp (e.g., “2025-09-12 09:41:09”)
closeDate
datetime
required
Exit/sell timestamp (e.g., “2025-09-18 02:12:02”)
pnl
number
required
Profit/loss amount in dollars (can be negative)

Optional Fields

accountNumber
string
Account identifier (numbers, letters, or alphanumeric)
entryId
string
Unique buy transaction ID
closeId
string
Unique sell transaction ID
timeInPosition
number
Duration in seconds
side
string
Trade direction: “buy”, “sell”, “long”, or “short”
commission
number
Trading fees in dollars

How It Works

AI Mapping Process

  1. Upload CSV: User uploads CSV file with trade data
  2. Parse Headers: Extract column names from CSV
  3. Analyze Data: AI examines column names and sample data
  4. Context Analysis: Consider column order and relationships
  5. Generate Mappings: AI suggests field mappings
  6. User Review: User can accept or modify mappings
  7. Import Trades: Process CSV with confirmed mappings

Duplicate Column Handling

The AI can distinguish between duplicate column names using position:
Instrument,Date,Prix,Date,Prix,P&L
ES,2025-09-12 09:41:09,5000.00,2025-09-12 10:15:22,5010.00,50.00
In this example, there are two “Date” columns and two “Prix” (price) columns. The AI uses position to map:
  • First “Date” → entryDate
  • First “Prix” → entryPrice
  • Second “Date” → closeDate
  • Second “Prix” → closePrice

API Reference

POST /api/ai/mappings

Generate AI-powered column mappings for CSV import. Request Body:
{
  fieldColumns: string[]          // CSV column headers
  firstRows: Record<string, string>[]  // Sample data (first 5 rows)
}
Response: Streaming text response with JSON object:
{
  accountNumber: string | null    // Mapped column name
  instrument: string | null
  entryId: string | null
  closeId: string | null
  quantity: string | null
  entryPrice: string | null
  closePrice: string | null
  entryDate: string | null
  closeDate: string | null
  pnl: string | null
  timeInPosition: string | null
  side: string | null
  commission: string | null
}
Example:
const response = await fetch('/api/ai/mappings', {
  method: 'POST',
  headers: { 'Content-Type': 'application/json' },
  body: JSON.stringify({
    fieldColumns: ['Symbol', 'Entry Time', 'Entry Price', 'Exit Time', 'Exit Price', 'Profit'],
    firstRows: [
      {
        'Symbol': 'ES',
        'Entry Time': '2025-09-12 09:41:09',
        'Entry Price': '5000.00',
        'Exit Time': '2025-09-12 10:15:22',
        'Exit Price': '5010.00',
        'Profit': '50.00'
      },
      // ... more sample rows
    ]
  })
})

// Response is streamed
const result = await response.json()
console.log('AI Mappings:', result)
See app/api/ai/mappings/route.ts:9-98

AI Mapping Algorithm

Prompt Engineering

The AI uses a sophisticated prompt to understand trade data:
const prompt = `
You are a trading data expert. Analyze the CSV columns and their data patterns 
to map them to the correct database fields. Look at BOTH the column names AND 
the actual data values to make intelligent mappings.

CRITICAL: Analyze column CONTEXT and ORDER, not just names:
- Column order matters: entryDate → entryPrice → closeDate → closePrice is typical
- If you see duplicate column names (like "Prix"), use POSITION to distinguish:
  * First "Prix" after entryDate = entryPrice
  * Second "Prix" after closeDate = closePrice
- Look for logical sequences: Date → Price → Date → Price
- Analyze data patterns:
  * Date columns: timestamps like "2025-09-12 09:41:09"
  * Price columns: decimal numbers
  * ID columns: unique identifiers (often numeric)
  * Quantity columns: trade sizes (decimal numbers)
  * PnL columns: profit/loss amounts (can be negative)

IMPORTANT: For duplicate column names, you MUST include the column position 
(1-based index) in your response. Format: "ColumnName_Position" 
(e.g., "Prix_1", "Prix_2")

Column order and context:
${fieldColumns.map((col, index) => `${index + 1}. ${col}`).join('\n')}

Sample data (first few rows):
${firstRows.map((row, index) => 
  `Row ${index + 1}: ${Object.entries(row)
    .map(([col, val]) => `${col}: "${val}"`)
    .join(", ")}`
).join('\n')}
`
See app/api/ai/mappings/route.ts:39-84

Schema Validation

Mappings are validated using Zod schema:
import { z } from 'zod/v3'

export const mappingSchema = z.object({
  accountNumber: z.string().nullable()
    .describe("The account number or name associated with the trade"),
  instrument: z.string().nullable()
    .describe("The trading instrument (e.g., stock symbol, ticker)"),
  entryId: z.string().nullable()
    .describe("The unique identifier for the buy transaction"),
  closeId: z.string().nullable()
    .describe("The unique identifier for the sell transaction"),
  quantity: z.string().nullable()
    .describe("The number of units traded"),
  entryPrice: z.string().nullable()
    .describe("The price at which the instrument was bought"),
  closePrice: z.string().nullable()
    .describe("The price at which the instrument was sold"),
  entryDate: z.string().nullable()
    .describe("The date when the entry / buy transaction occurred"),
  closeDate: z.string().nullable()
    .describe("The date when the close / sell transaction occurred"),
  pnl: z.string().nullable()
    .describe("The profit or loss from the trade (gross PnL when commission exists)"),
  timeInPosition: z.string().nullable()
    .describe("The duration for which the position was held"),
  side: z.string().nullable()
    .describe("The entry side of the trade (e.g., buy or sell)"),
  commission: z.string().nullable()
    .describe("The commission charged for the trade"),
})
See app/api/ai/mappings/schema.ts:1-56

UI Component

ColumnMapping Component

The UI provides interactive column mapping:
import ColumnMapping from '@/app/[locale]/dashboard/components/import/column-mapping'

<ColumnMapping
  headers={csvHeaders}
  csvData={csvRows}
  mappings={mappings}
  setMappings={setMappings}
  error={error}
  importType="csv"
/>
See app/[locale]/dashboard/components/import/column-mapping.tsx:59-316

Features

  1. AI Auto-Mapping Button: Triggers AI analysis
  2. Visual Field Status: Shows required vs optional fields
  3. Sample Data Preview: Displays first 3 rows for each column
  4. Manual Override: Dropdown to manually select mappings
  5. Duplicate Handling: Shows position for duplicate columns (e.g., “Price (1)”, “Price (2)”)
  6. Unmapped Fields Banner: Highlights missing required fields

Example Usage

const [mappings, setMappings] = useState<Record<string, string>>({})

// User clicks "Use AI for mapping"
const handleAIMapping = async () => {
  const sampleData = csvData.slice(1, 6).map(row => {
    const rowObj: Record<string, string> = {}
    headers.forEach((header, index) => {
      rowObj[header] = row[index] || ''
    })
    return rowObj
  })
  
  const response = await fetch('/api/ai/mappings', {
    method: 'POST',
    body: JSON.stringify({ 
      fieldColumns: headers, 
      firstRows: sampleData 
    })
  })
  
  const aiMappings = await response.json()
  setMappings(aiMappings)
}
See app/[locale]/dashboard/components/import/column-mapping.tsx:190-208

Duplicate Column Resolution

Unique Column Identifiers

Columns are tracked using position-based IDs:
const createUniqueColumnId = (header: string, index: number) => 
  `${header}_${index}`

const getColumnDisplayName = (header: string, index: number, headers: string[]) => {
  const duplicateCount = headers.filter(h => h === header).length
  return duplicateCount > 1 ? `${header} (${index + 1})` : header
}

// Example
const headers = ['Date', 'Price', 'Date', 'Price']
getColumnDisplayName('Date', 0, headers)  // "Date (1)"
getColumnDisplayName('Price', 1, headers) // "Price (1)"
getColumnDisplayName('Date', 2, headers)  // "Date (2)"
getColumnDisplayName('Price', 3, headers) // "Price (2)"
See app/[locale]/dashboard/components/import/column-mapping.tsx:51-57

AI Position-Based Mapping

The AI response includes position information:
// AI response for duplicate columns
{
  entryDate: "Date_1",    // First Date column (position 1)
  entryPrice: "Price_1",   // First Price column (position 1)
  closeDate: "Date_2",     // Second Date column (position 2)
  closePrice: "Price_2"    // Second Price column (position 2)
}

// Parsing logic
const positionMatch = headerValue.match(/^(.+)_(\d+)$/)
if (positionMatch) {
  const [, headerName, positionStr] = positionMatch
  const position = parseInt(positionStr, 10) - 1  // Convert to 0-based
  
  if (headers[position] === headerName) {
    const uniqueId = createUniqueColumnId(headerName, position)
    mappings[uniqueId] = destinationColumn
  }
}
See app/[locale]/dashboard/components/import/column-mapping.tsx:82-104

Configuration

Column Defaults

Default mappings for common column names:
const columnConfig = {
  "accountNumber": { 
    defaultMapping: ["account", "accountnumber"], 
    required: false 
  },
  "instrument": { 
    defaultMapping: ["symbol", "ticker"], 
    required: true 
  },
  "entryId": { 
    defaultMapping: ["buyid", "buyorderid"], 
    required: false 
  },
  "closeId": { 
    defaultMapping: ["sellid", "sellorderid"], 
    required: false 
  },
  "quantity": { 
    defaultMapping: ["qty", "amount"], 
    required: true 
  },
  "entryPrice": { 
    defaultMapping: ["buyprice", "entryprice"], 
    required: true 
  },
  "closePrice": { 
    defaultMapping: ["sellprice", "exitprice"], 
    required: true 
  },
  "entryDate": { 
    defaultMapping: ["buydate", "entrydate"], 
    required: true 
  },
  "closeDate": { 
    defaultMapping: ["selldate", "exitdate"], 
    required: true 
  },
  "pnl": { 
    defaultMapping: ["pnl", "profit"], 
    required: true 
  },
  "timeInPosition": { 
    defaultMapping: ["timeinposition", "duration"], 
    required: false 
  },
  "side": { 
    defaultMapping: ["side", "direction"], 
    required: false 
  },
  "commission": { 
    defaultMapping: ["commission", "fee"], 
    required: false 
  },
}
See app/[locale]/dashboard/components/import/column-mapping.tsx:23-37

Error Handling

Error: Missing required fieldsCause: One or more required fields are not mappedSolution: Ensure all required fields have mappings:
const requiredFields = [
  'instrument', 'quantity', 'entryPrice', 'closePrice',
  'entryDate', 'closeDate', 'pnl'
]

const missingFields = requiredFields.filter(
  field => !Object.values(mappings).includes(field)
)

if (missingFields.length > 0) {
  console.error('Missing fields:', missingFields)
}
Error: Error generating AI mappingsCauses:
  • AI API timeout
  • Invalid CSV format
  • Insufficient sample data
Solution:
  • Retry the AI mapping
  • Manually map fields
  • Ensure CSV has at least 5 sample rows
Error: Multiple columns mapped to same fieldCause: Manual mapping conflictSolution: The UI automatically prevents this, but in code:
// Remove existing mapping before adding new one
Object.keys(mappings).forEach(key => {
  if (mappings[key] === destinationField) {
    delete mappings[key]
  }
})
mappings[uniqueColumnId] = destinationField

Best Practices

Clean Data

Remove headers, footers, and summary rows before uploading CSV

Consistent Format

Use consistent date/time formats across all rows

Sample Size

Include at least 5 sample rows for accurate AI mapping

Review Mappings

Always review AI suggestions before importing

Example CSV Formats

Standard Format

Symbol,EntryDate,EntryPrice,ExitDate,ExitPrice,Quantity,PnL,Commission
ES,2025-09-12 09:41:09,5000.00,2025-09-12 10:15:22,5010.00,1,50.00,4.20
NQ,2025-09-12 11:30:45,18000.00,2025-09-12 12:05:33,18050.00,2,100.00,8.40

Format with Duplicate Columns

Instrument,Date,Time,Price,Date,Time,Price,P&L
ES,2025-09-12,09:41:09,5000.00,2025-09-12,10:15:22,5010.00,50.00
NQ,2025-09-12,11:30:45,18000.00,2025-09-12,12:05:33,18050.00,100.00

Foreign Language Format

Instrument,Date Entrée,Prix,Date Sortie,Prix,Quantité,Profit
ES,2025-09-12 09:41:09,5000.00,2025-09-12 10:15:22,5010.00,1,50.00
NQ,2025-09-12 11:30:45,18000.00,2025-09-12 12:05:33,18050.00,2,100.00
The AI can handle multiple languages and duplicate column names automatically.

Troubleshooting

AI Mapping Not Working

  1. Check sample data quality:
console.log('Sample rows:', firstRows)
// Ensure data is representative of the full CSV
  1. Verify column count:
console.log('Headers:', fieldColumns.length)
console.log('Data columns:', firstRows[0] ? Object.keys(firstRows[0]).length : 0)
// Should match
  1. Test with manual mapping first:
  • Manually map fields to verify data structure
  • Then use AI to see if it matches your mapping

Incorrect Date Parsing

If dates are not being recognized:
  1. Check date format:
✅ Correct: 2025-09-12 09:41:09
✅ Correct: 2025-09-12T09:41:09Z
✅ Correct: 09/12/2025 09:41:09
❌ Incorrect: 12-Sep-2025
❌ Incorrect: September 12, 2025
  1. Add explicit format hint:
  • Include date format in column name (e.g., “EntryDate (YYYY-MM-DD)”)
  • AI will recognize the format hint

Commission Not Calculated

If commission is missing:
  1. Check if commission column exists:
const hasCommission = Object.values(mappings).includes('commission')
if (!hasCommission) {
  console.log('Commission field not mapped - trades will have 0 commission')
}
  1. Verify commission values are numeric:
✅ Correct: 4.20
✅ Correct: 4.2
❌ Incorrect: $4.20
❌ Incorrect: 4.20 USD

Next Steps

View Imported Trades

Analyze your imported CSV trades

API Integration

Try automatic syncing with Tradovate

Build docs developers (and LLMs) love