CSV
CSV
CSV (Comma-Separated Values) is a simple text format for tabular data. HaasScript provides the ParseCsv() command to parse CSV strings or fetch CSV data from URLs.
Basic CSV Parsing
Parse a CSV string using ParseCsv():
local csvString = "BTC-USDT;50000;2.5\nETH-USDT;3000;1.8"
local data = ParseCsv(csvString)
-- Access parsed data (returned as object with numeric keys)
Log(data[1][1]) -- BTC-USDT
Log(data[1][2]) -- 50000
Log(data[1][3]) -- 2.5
Important: The default delimiter is semicolon (;), not comma. Row delimiter defaults to newline (\n).
CSV with Headers
Use the hasHeaders parameter to skip the first row (header row):
local csvString = [[symbol;price;change
BTC-USDT;50000;2.5
ETH-USDT;3000;1.8]]
local data = ParseCsv(csvString, true) -- true = has headers
-- Header row is skipped, data starts at row 1
Log(data[1][1]) -- BTC-USDT
Log(data[1][2]) -- 50000
Log(data[1][3]) -- 2.5
Log(data[2][1]) -- ETH-USDT
Log(data[2][2]) -- 3000
Log(data[2][3]) -- 1.8
Custom Delimiters
Specify custom column and row delimiters:
local csvString = "BTC-USDT,50000,2.5\nETH-USDT,3000,1.8"
local data = ParseCsv(csvString, false, ',', '\n')
-- Use comma as column delimiter
Log(data[1][1]) -- BTC-USDT
Log(data[1][2]) -- 50000
Log(data[1][3]) -- 2.5
Parameters in order:
-
csv- CSV string or URL -
hasHeaders- Boolean, true if first row contains headers -
columnDelimiter- Column separator (default:;) -
rowDelimiter- Row separator (default:\n)
Fetching CSV from URLs
ParseCsv() can fetch CSV data directly from URLs:
local csvUrl = 'https://data.example.com/prices.csv'
local data = ParseCsv(csvUrl, true)
-- Iterate through rows
for i = 1, #data do
Log(data[i][1] .. ': ' .. data[i][2])
end
Practical Examples
Importing Historical Price Data
-- Fetch historical CSV data
local historyUrl = 'https://data.example.com/historical/BTC-USDT.csv'
local data = ParseCsv(historyUrl, true, ',')
-- Extract prices from CSV (assuming close price is column 2)
local prices = NewArray()
for i = 1, #data do
local price = tonumber(data[i][2])
ArrayAdd(prices, price)
end
-- Calculate indicators on imported data
local rsi = RSI(prices, 14)
local sma = SMA(prices, 20)
Log('Current RSI: ' .. rsi[1])
Log('Current SMA: ' .. sma[1])
Multi-Asset Price List
-- Load price list from CSV
local priceListUrl = 'https://data.example.com/pricelist.csv'
local data = ParseCsv(priceListUrl, true, ',')
-- CSV structure: symbol,price,change
-- Example:
-- symbol,price,change
-- BTC-USDT,50000,2.5
-- ETH-USDT,3000,1.8
-- Process each asset
for i = 1, #data do
local symbol = data[i][1]
local price = tonumber(data[i][2])
local change = tonumber(data[i][3])
Log(symbol .. ': $' .. price .. ' (' .. change .. '%)')
-- Check if current symbol matches our bot
if symbol == CurrentMarket() then
local currentPrice = CurrentPrice()
local priceDiff = math.abs(currentPrice - price) / currentPrice * 100
if priceDiff > 1 then
Log('Price discrepancy detected: ' .. priceDiff .. '%')
end
end
end
Configuration File
-- Load strategy configuration from CSV
local configCsv = [[param,value
RSI_Length,14
RSI_Overbought,70
RSI_Oversold,30
StopLoss_Percent,2.0
Position_Size,100]]
local config = ParseCsv(configCsv, true, ',')
-- Create configuration object
local settings = {}
for i = 1, #config do
local key = config[i][1]
local value = config[i][2]
-- Convert numeric values
if tonumber(value) ~= nil then
settings[key] = tonumber(value)
else
settings[key] = value
end
end
-- Use settings in strategy
local rsiLength = settings.RSI_Length
local rsiOverbought = settings.RSI_Overbought
local stopLossPercent = settings.StopLoss_Percent
Log('RSI Length: ' .. rsiLength)
Log('Stop Loss: ' .. stopLossPercent .. '%')
Trade Journal Export
-- Prepare CSV data for export
local csvData = "time,type,price,rsi\n"
-- Add recent trades
local tradeHistory = Load('tradeHistory', {})
for i = 1, #tradeHistory do
local trade = tradeHistory[i]
csvData = csvData .. trade.time .. "," .. trade.type .. "," .. trade.price .. "," .. trade.rsi .. "\n"
end
-- Note: CSV export would need external tools or API integration
-- This example shows how to format CSV data
Log('CSV data prepared: ' .. #tradeHistory .. ' trades')
Correlation Analysis
-- Load price data for multiple assets
local btcUrl = 'https://data.example.com/BTC.csv'
local ethUrl = 'https://data.example.com/ETH.csv'
local btcData = ParseCsv(btcUrl, true, ',')
local ethData = ParseCsv(ethUrl, true, ',')
-- Extract price arrays (assuming close price is column 2)
local btcPrices = NewArray()
local ethPrices = NewArray()
for i = 1, math.min(#btcData, #ethData) do
ArrayAdd(btcPrices, tonumber(btcData[i][2]))
ArrayAdd(ethPrices, tonumber(ethData[i][2]))
end
-- Calculate correlation (simplified)
local n = #btcPrices
local sumBtc = ArraySum(btcPrices)
local sumEth = ArraySum(ethPrices)
local avgBtc = sumBtc / n
local avgEth = sumEth / n
Log('BTC avg: ' .. avgBtc)
Log('ETH avg: ' .. avgEth)
Working with Parsed CSV Data
Accessing Without Headers
When hasHeaders is false (default), use numeric indices:
local csvString = "BTC-USDT;50000;2.5\nETH-USDT;3000;1.8"
local data = ParseCsv(csvString) -- hasHeaders = false
-- Access by row and column indices
for row = 1, #data do
for col = 1, #data[row] do
Log('Row ' .. row .. ', Col ' .. col .. ': ' .. data[row][col])
end
end
Accessing With Headers
When hasHeaders is true, the header row is skipped. Use numeric indices:
local csvString = [[symbol;price;change
BTC-USDT;50000;2.5
ETH-USDT;3000;1.8]]
local data = ParseCsv(csvString, true)
-- Access by row and column indices
for row = 1, #data do
Log(data[row][1] .. ': ' .. data[row][2])
end
Error Handling
Always handle potential parsing errors:
local csvUrl = 'https://data.example.com/data.csv'
local success, data = pcall(function()
return ParseCsv(csvUrl, true, ',')
end)
if success then
if data ~= nil and #data > 0 then
Log('CSV parsed successfully: ' .. #data .. ' rows')
else
Log('Error: CSV data is empty')
end
else
Log('Error parsing CSV: ' .. tostring(data))
end
Best Practices
Validate CSV structure:
local data = ParseCsv(csvUrl, true, ',')
if data == nil or #data == 0 then
Log('Error: No data in CSV')
return
end
-- Check required columns exist
if #data[1] < 2 then
Log('Error: CSV missing required columns')
return
end
Convert data types:
local data = ParseCsv(csvUrl, true, ',')
-- Convert string values to numbers (column 2 = price, column 4 = volume)
for i = 1, #data do
data[i][2] = tonumber(data[i][2])
data[i][4] = tonumber(data[i][4])
end
Cache CSV data:
local lastFetchTime = Load('lastCsvFetch')
local cachedData = Load('cachedCsvData')
local currentTime = Time()
local fetchInterval = 3600 -- 1 hour
if lastFetchTime == nil or currentTime >= lastFetchTime + fetchInterval then
local newData = ParseCsv(csvUrl, true, ',')
Save('cachedCsvData', newData)
Save('lastCsvFetch', currentTime)
cachedData = newData
end
-- Use cached data
if cachedData ~= nil then
Log('Using cached CSV data')
end
Document your CSV format:
-- === CSV FORMAT DOCUMENTATION ===
-- URL: https://data.example.com/prices.csv
-- Delimiter: comma (,)
-- Headers: true
--
-- Columns:
-- - symbol: string - Trading pair symbol
-- - price: number - Current price
-- - change: number - 24h change percentage
-- - volume: number - 24h trading volume
-- - timestamp: number - Unix timestamp
Common Mistakes
Wrong delimiter:
-- Wrong: Using comma with semicolon-delimited CSV
local data = ParseCsv(csvString, false, ',')
-- Correct: Use the actual delimiter
local data = ParseCsv(csvString) -- Default is semicolon
Not converting types:
-- Wrong: CSV values are strings
local total = data[1][2] + data[2][2] -- String concatenation
-- Correct: Convert to numbers
local total = tonumber(data[1][2]) + tonumber(data[2][2])
Assuming headers exist:
-- Wrong: Headers are skipped, not used as keys
local price = data[1].price -- nil
-- Correct: Use numeric indices
local price = data[1][2]
Forgetting to handle empty values:
-- Risky: CSV may have empty cells
local price = tonumber(data[i][2])
-- Better: Check for nil/empty
local priceStr = data[i][2]
local price = priceStr ~= nil and priceStr ~= "" and tonumber(priceStr) or 0
CSV vs JSON
| Feature | CSV | JSON |
|---|---|---|
| Structure | Tabular (rows/columns) | Nested objects/arrays |
| Best for | Simple lists, configuration | Complex data structures |
| Human-readable | Yes | Yes |
| File size | Smaller | Larger |
| Delimiters | Configurable | N/A (structured) |
| Headers | Optional | Implicit via keys |