📰 Latest: HaasOnline Academy Is Back — Structured Education for Smarter Trade Bots
Account
Using Data

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:

  1. csv - CSV string or URL
  2. hasHeaders - Boolean, true if first row contains headers
  3. columnDelimiter - Column separator (default: ;)
  4. 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