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
Trading symbol or ticker (e.g., ES, NQ, EURUSD, BTCUSD)
Number of contracts or units traded
Buy/entry price for the trade
Sell/exit price for the trade
Entry/buy timestamp (e.g., “2025-09-12 09:41:09”)
Exit/sell timestamp (e.g., “2025-09-18 02:12:02”)
Profit/loss amount in dollars (can be negative)
Optional Fields
Account identifier (numbers, letters, or alphanumeric)
Unique buy transaction ID
Unique sell transaction ID
Trade direction: “buy”, “sell”, “long”, or “short”
How It Works
AI Mapping Process
Upload CSV : User uploads CSV file with trade data
Parse Headers : Extract column names from CSV
Analyze Data : AI examines column names and sample data
Context Analysis : Consider column order and relationships
Generate Mappings : AI suggests field mappings
User Review : User can accept or modify mappings
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
AI Auto-Mapping Button : Triggers AI analysis
Visual Field Status : Shows required vs optional fields
Sample Data Preview : Displays first 3 rows for each column
Manual Override : Dropdown to manually select mappings
Duplicate Handling : Shows position for duplicate columns (e.g., “Price (1)”, “Price (2)”)
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
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
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
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
Check sample data quality :
console . log ( 'Sample rows:' , firstRows )
// Ensure data is representative of the full CSV
Verify column count :
console . log ( 'Headers:' , fieldColumns . length )
console . log ( 'Data columns:' , firstRows [ 0 ] ? Object . keys ( firstRows [ 0 ]). length : 0 )
// Should match
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:
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
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:
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' )
}
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