import live OHLC data from external .CSV (.java code generated by AI)

? Universal CSV Data Reader v3.0 - Free MotiveWave Study

Overview

I'm sharing a universal CSV reader study that I developed for MotiveWave. This study allows you to load and display data from ANY CSV file with real-time updates. No more creating
separate studies for each symbol!

✨ Key Features

- Universal File Support: Select any CSV file through MotiveWave's native file dialog
- Symbol Filtering: Display any symbol from your CSV (configurable)
- Real-Time Updates: Automatic refresh every 5 seconds
- Memory Efficient: Historical bars use close-only, recent bars full OHLC
- Professional Architecture: Based on proven hybrid pattern with incremental loading

? CSV Format Expected

Date,Time,Open,High,Low,Close,Volume,Symbol
2025-08-19,08:16:28,0.1234,0.1250,0.1230,0.1245,1000,YOUR_SYMBOL
The study filters by the Symbol column (8th column) to display only the data you want.

? Installation Instructions

1. Copy the Java code below
2. Save as CsvDataLiveV3_0.java in your MotiveWave SDK project
3. Add to path: your-project/lib/src/main/java/com/volatility/studies/
4. Compile with your SDK project
5. Restart MotiveWave
6. Find in: Studies → CSV Data Studies → CSV Data Live v3.0

? How to Use

1. Add the study to any chart
2. Open study settings
3. In "Data Source" tab:
- Click "CSV Data File" to select your CSV file
- Enter "Symbol to Display" (e.g., SPX_IV_ATM, EUR_USD, etc.)
4. Click Apply
5. The study will load historical data and update in real-time

? Configuration Options

- CSV Data File: Browse and select your data file
- Symbol to Display: Enter the exact symbol name from your CSV
- Live Bars Count: Number of recent bars with full OHLC (default: 15)
- Full History OHLC: Enable for complete OHLC on all bars (memory intensive)
- Colors: Customizable up/down/history candle colors

? Use Cases

- Import custom volatility data
- Display proprietary indicators
- Load external market data
- Backtest with historical CSV data
- Real-time monitoring of custom metrics

⚙️ Technical Details

- Incremental Loading: Only reads new data, not entire file each time
- Hybrid Architecture: Optimized for both memory and performance
- Cache System: Intelligent caching of recent OHLC data
- Timezone Support: Handles UTC data with local display
 
? Source Code

package com.volatility.studies;

import com.motivewave.platform.sdk.common.DataContext;
import com.motivewave.platform.sdk.common.Defaults;
import com.motivewave.platform.sdk.common.Enums;
import com.motivewave.platform.sdk.common.PriceData;
import com.motivewave.platform.sdk.common.desc.ValueDescriptor;
import com.motivewave.platform.sdk.common.desc.PriceBarDescriptor;
import com.motivewave.platform.sdk.common.desc.ColorDescriptor;
import com.motivewave.platform.sdk.common.desc.IntegerDescriptor;
import com.motivewave.platform.sdk.common.desc.BooleanDescriptor;
import com.motivewave.platform.sdk.common.desc.FileDescriptor;
import com.motivewave.platform.sdk.common.desc.StringDescriptor;
import com.motivewave.platform.sdk.study.Study;
import com.motivewave.platform.sdk.study.StudyHeader;
import java.util.*;
import java.time.LocalDateTime;
import java.time.ZoneId;
import java.time.format.DateTimeFormatter;
import java.io.*;

/**
* CSV DATA LIVE V3.0 - Universal configurable study
*
* GENERIC STUDY FOR ALL CSV SYMBOLS:
* - User selects CSV file via FileDescriptor
* - User chooses symbol to display via StringDescriptor
* - Automatic filtering by Symbol column (8th column)
* - Optimized real-time hybrid architecture
*
* @version 3.0
* @since 2025-08-21
*/
@StudyHeader(
namespace = "com.volatility.studies",
id = "CSV_DATA_LIVE_V3_0",
name = "CSV Data Live v3.0",
desc = "Universal CSV Reader with Symbol Selection",
menu = "CSV Data Studies",
overlay = false,
studyOverlay = true,
requiresBarUpdates = true
)
public class CsvDataLiveV3_0 extends Study {

// Values for MotiveWave pattern
enum Values {
PRICE_BAR,
OPEN,
HIGH,
LOW,
CLOSE
}

// User configurable parameters
final static String CSV_FILE_KEY = "csvFile";
final static String TARGET_SYMBOL_KEY = "targetSymbol";
final static String LIVE_BARS_COUNT = "liveBarsCount";
final static String FULL_HISTORY_MODE = "fullHistoryMode";
final static String UP_COLOR = "upColor";
final static String DOWN_COLOR = "downColor";
final static String HISTORY_COLOR = "historyColor";

// Configuration
private static final long CHECK_INTERVAL_MS = 5000; // 5 seconds

// Hybrid cache system
private Map<Long, Double> historicalCloses = new LinkedHashMap<>();
private Map<Long, Double> csvTimestampValues = new LinkedHashMap<>();
private Map<Integer, double[]> recentOhlcCache = new HashMap<>();

// State variables
private volatile long lastFileModification = 0;
private volatile long lastFileSize = 0;
private volatile long lastCheckTime = 0;
private volatile long lastProcessedPosition = 0;
private volatile boolean initialLoadDone = false;

// User parameters
private int liveBarsCount = 15;
private boolean fullHistoryMode = false;

// Timezone for logging
private static final ZoneId PARIS_ZONE = ZoneId.of("Europe/Paris");
private String paris(long ms) {
return java.time.Instant.ofEpochMilli(ms)
.atZone(PARIS_ZONE)
.format(DateTimeFormatter.ofPattern("HH:mm:ss"));
}

/**
* Get CSV file path from user configuration
*/
private String getCsvPath() {
File csvFile = getSettings().getFile(CSV_FILE_KEY);
if (csvFile == null) {
return null;
}
return csvFile.getAbsolutePath();
}

/**
* Get target symbol from user configuration
*/
private String getTargetSymbol() {
String symbol = getSettings().getString(TARGET_SYMBOL_KEY);
if (symbol == null || symbol.trim().isEmpty()) {
return null;
}
return symbol.trim();
}

@Override
public void initialize(Defaults defaults) {
info("CSV DATA LIVE V3.0 INITIALIZE - Universal CSV Reader");

var sd = createSD();
var tab = sd.addTab("Configuration");

// Data source configuration
var dataSource = tab.addGroup("Data Source");

// CSV file selector with filter
FileDescriptor fileDesc = new FileDescriptor(CSV_FILE_KEY, "CSV Data File", null);
fileDesc.setExtensions(Arrays.asList("*.csv", "*.CSV"));
fileDesc.setFilterName("CSV Files (*.csv)");
dataSource.addRow(fileDesc);

// Symbol input field
dataSource.addRow(new StringDescriptor(TARGET_SYMBOL_KEY, "Symbol to Display", ""));

// Algorithm settings
var settings = tab.addGroup("Algorithm Settings");
settings.addRow(new IntegerDescriptor(LIVE_BARS_COUNT, "Live Bars Count",
15, 5, 100, 1));
settings.addRow(new BooleanDescriptor(FULL_HISTORY_MODE, "Full History OHLC",
false));

// Display settings
var display = tab.addGroup("Display Settings");
display.addRow(new PriceBarDescriptor("CSV_PRICE_BAR", "CSV Data Candlesticks",
null, Enums.BarInput.CLOSE, true, false));

// Color settings
var colors = tab.addGroup("Colors");
colors.addRow(new ColorDescriptor(UP_COLOR, "Up Color",
new java.awt.Color(34, 139, 34), false, true));
colors.addRow(new ColorDescriptor(DOWN_COLOR, "Down Color",
new java.awt.Color(220, 20, 60), false, true));
colors.addRow(new ColorDescriptor(HISTORY_COLOR, "History Color",
new java.awt.Color(100, 100, 100), false, true));

// Runtime descriptor
var desc = createRD();

desc.exportValue(new ValueDescriptor(Values.OPEN, "CSV Data Open", new String[] {}));
desc.exportValue(new ValueDescriptor(Values.HIGH, "CSV Data High", new String[] {}));
desc.exportValue(new ValueDescriptor(Values.LOW, "CSV Data Low", new String[] {}));
desc.exportValue(new ValueDescriptor(Values.CLOSE, "CSV Data Close", new String[] {}));

desc.declarePriceBar(Values.PRICE_BAR, "CSV_PRICE_BAR");

desc.setMinTick(0.01);
desc.setTopInsetPixels(10);
desc.setBottomInsetPixels(10);
desc.setRangeKeys(Values.HIGH, Values.LOW);

info("CSV Data Live V3.0 - Please configure: 1) CSV file path, 2) Symbol to display");
}

/**
* Load historical data with memory optimization
*/
private void loadHistoricalData() {
if (initialLoadDone) return;

String csvPath = getCsvPath();
if (csvPath == null) {
info("No CSV file selected. Please configure in study settings.");
return;
}

String targetSymbol = getTargetSymbol();
if (targetSymbol == null) {
info("No symbol specified. Please configure in study settings.");
return;
}

try {
File csvFile = new File(csvPath);
if (!csvFile.exists()) {
info("CSV file not found: " + csvPath);
return;
}

info(String.format("Loading data for symbol: %s from %s", targetSymbol, csvPath));

BufferedReader reader = new BufferedReader(new FileReader(csvFile));
String line;
int linesLoaded = 0;

// Skip header
reader.readLine();

while ((line = reader.readLine()) != null) {
String[] parts = line.split(",");
if (parts.length >= 8 && targetSymbol.equals(parts[7].trim())) {
try {
String date = parts[0].trim();
String time = parts[1].trim();
double close = Double.parseDouble(parts[5].trim()) * 100.0;

String timestampStr = date + "T" + time;
LocalDateTime dateTime = LocalDateTime.parse(timestampStr);
long timestamp = dateTime.atZone(ZoneId.of("UTC")).toInstant().toEpochMilli();

historicalCloses.put(timestamp, close);
linesLoaded++;

} catch (Exception e) {
// Ignore invalid lines
}
}
}

reader.close();
lastFileSize = csvFile.length();
lastFileModification = csvFile.lastModified();
lastProcessedPosition = csvFile.length();

info(String.format("Historical load: %d data points for %s",
linesLoaded, targetSymbol));

initialLoadDone = true;

} catch (Exception e) {
info("Error loading historical data: " + e.getMessage());
}
}
 
/**
* Incremental loading for new data only
*/
private void loadIncrementalData() {
String csvPath = getCsvPath();
String targetSymbol = getTargetSymbol();

if (csvPath == null || targetSymbol == null) {
return;
}

try {
File csvFile = new File(csvPath);

if (!csvFile.exists()) {
return;
}

if (csvFile.length() < lastProcessedPosition) {
// File was rewritten
info("CSV file reset, reloading...");
lastProcessedPosition = 0;
historicalCloses.clear();
csvTimestampValues.clear();
initialLoadDone = false;
loadHistoricalData();
return;
}

if (csvFile.length() == lastProcessedPosition) {
return; // No new data
}

RandomAccessFile raf = new RandomAccessFile(csvFile, "r");
raf.seek(lastProcessedPosition);

String line;
int newLines = 0;

while ((line = raf.readLine()) != null) {
String[] parts = line.split(",");
if (parts.length >= 8 && targetSymbol.equals(parts[7].trim())) {
try {
String date = parts[0].trim();
String time = parts[1].trim();
double close = Double.parseDouble(parts[5].trim()) * 100.0;

String timestampStr = date + "T" + time;
LocalDateTime dateTime = LocalDateTime.parse(timestampStr);
long timestamp = dateTime.atZone(ZoneId.of("UTC")).toInstant().toEpochMilli();

csvTimestampValues.put(timestamp, close);
newLines++;

} catch (Exception e) {
// Ignore
}
}
}

lastProcessedPosition = raf.getFilePointer();
raf.close();

if (newLines > 0) {
info(String.format("Incremental: +%d new data points for %s", newLines, targetSymbol));
}

} catch (Exception e) {
debug("Incremental load error: " + e.getMessage());
}
}

/**
* Calculate OHLC for recent bars
*/
private double[] calculateOHLCForBar(long barStartTime, long barEndTime) {
List<Double> valuesInBar = new ArrayList<>();

// Combine historical + real-time
for (Map.Entry<Long, Double> entry : historicalCloses.entrySet()) {
long timestamp = entry.getKey();
if (timestamp >= barStartTime && timestamp < barEndTime) {
valuesInBar.add(entry.getValue());
}
}

for (Map.Entry<Long, Double> entry : csvTimestampValues.entrySet()) {
long timestamp = entry.getKey();
if (timestamp >= barStartTime && timestamp < barEndTime) {
valuesInBar.add(entry.getValue());
}
}

if (valuesInBar.isEmpty()) {
return null;
}

double open = valuesInBar.get(0);
double close = valuesInBar.get(valuesInBar.size() - 1);
double high = Collections.max(valuesInBar);
double low = Collections.min(valuesInBar);

return new double[]{open, high, low, close};
}

/**
* Create historical candle (close-only)
*/
private double[] createHistoricalCandle(long barStartTime, long barEndTime) {
Double closeValue = null;

for (Map.Entry<Long, Double> entry : historicalCloses.entrySet()) {
long timestamp = entry.getKey();
if (timestamp >= barStartTime && timestamp < barEndTime) {
closeValue = entry.getValue();
}
}

for (Map.Entry<Long, Double> entry : csvTimestampValues.entrySet()) {
long timestamp = entry.getKey();
if (timestamp >= barStartTime && timestamp < barEndTime) {
closeValue = entry.getValue();
}
}

if (closeValue == null) return null;

// Flat candle: open=high=low=close
return new double[]{closeValue, closeValue, closeValue, closeValue};
}

private java.awt.Color getCandleColor(boolean isUp) {
if (isUp) {
return getSettings().getColor(UP_COLOR, new java.awt.Color(34, 139, 34));
} else {
return getSettings().getColor(DOWN_COLOR, new java.awt.Color(220, 20, 60));
}
}

private java.awt.Color getHistoryColor() {
return getSettings().getColor(HISTORY_COLOR, new java.awt.Color(100, 100, 100));
}

@Override
protected void calculateValues(DataContext ctx) {
var series = ctx.getDataSeries();
int size = series.size();

liveBarsCount = getSettings().getInteger(LIVE_BARS_COUNT, 15);
fullHistoryMode = getSettings().getBoolean(FULL_HISTORY_MODE, false);

String csvPath = getCsvPath();
String targetSymbol = getTargetSymbol();

if (csvPath == null || targetSymbol == null) {
info("Configuration incomplete. Please set CSV file and symbol in study settings.");
return;
}

info(String.format("CSV Data Live V3.0: %d bars, Symbol: %s (%s Paris)",
size, targetSymbol, paris(System.currentTimeMillis())));

if (!initialLoadDone) {
loadHistoricalData();
}

int transitionIndex = Math.max(0, size - liveBarsCount);

info(String.format("Strategy: bars 0-%d close-only, bars %d-%d full OHLC",
transitionIndex-1, transitionIndex, size-1));

int historicalBars = 0;
int ohlcBars = 0;

for (int i = 0; i < size; i++) {
if (series.isComplete(i)) continue;

long barStartTime = series.getStartTime(i);
long barEndTime = series.getEndTime(i);

if (i < transitionIndex && !fullHistoryMode) {
// Historical bars: close-only
double[] historicalOhlc = createHistoricalCandle(barStartTime, barEndTime);

if (historicalOhlc != null) {
PriceData historyPriceData = new PriceData(
(float)historicalOhlc[0], (float)historicalOhlc[1],
(float)historicalOhlc[2], (float)historicalOhlc[3],
getHistoryColor()
);

series.setValue(i, Values.PRICE_BAR, historyPriceData);
series.setFloat(i, Values.OPEN, (float)historicalOhlc[0]);
series.setFloat(i, Values.HIGH, (float)historicalOhlc[1]);
series.setFloat(i, Values.LOW, (float)historicalOhlc[2]);
series.setFloat(i, Values.CLOSE, (float)historicalOhlc[3]);

historicalBars++;
}

series.setComplete(i, true);

} else {
// Recent bars: full OHLC
double[] ohlc = calculateOHLCForBar(barStartTime, barEndTime);

if (ohlc != null) {
recentOhlcCache.put(i, ohlc);

boolean isUp = ohlc[3] >= ohlc[0];
java.awt.Color candleColor = getCandleColor(isUp);

PriceData priceData = new PriceData(
(float)ohlc[0], (float)ohlc[1],
(float)ohlc[2], (float)ohlc[3],
candleColor
);

series.setValue(i, Values.PRICE_BAR, priceData);
series.setFloat(i, Values.OPEN, (float)ohlc[0]);
series.setFloat(i, Values.HIGH, (float)ohlc[1]);
series.setFloat(i, Values.LOW, (float)ohlc[2]);
series.setFloat(i, Values.CLOSE, (float)ohlc[3]);

ohlcBars++;
}

boolean isRecentBar = (i >= size - liveBarsCount);
series.setComplete(i, !isRecentBar);
}
}

info(String.format("Rendered: %d historical (close-only), %d OHLC bars",
historicalBars, ohlcBars));
}
 
@Override
public void onBarUpdate(DataContext ctx) {
long now = System.currentTimeMillis();

if (now - lastCheckTime < CHECK_INTERVAL_MS) {
return;
}
lastCheckTime = now;

String targetSymbol = getTargetSymbol();
if (targetSymbol == null) {
return;
}

loadIncrementalData();

var series = ctx.getDataSeries();
int size = series.size();
int transitionIndex = Math.max(0, size - liveBarsCount);

for (int i = transitionIndex; i < size; i++) {
long barStartTime = series.getStartTime(i);
long barEndTime = series.getEndTime(i);

double[] ohlc = calculateOHLCForBar(barStartTime, barEndTime);
if (ohlc != null) {
recentOhlcCache.put(i, ohlc);

boolean isUp = ohlc[3] >= ohlc[0];
PriceData priceData = new PriceData(
(float)ohlc[0], (float)ohlc[1],
(float)ohlc[2], (float)ohlc[3],
getCandleColor(isUp)
);

series.setValue(i, Values.PRICE_BAR, priceData);
series.setFloat(i, Values.OPEN, (float)ohlc[0]);
series.setFloat(i, Values.HIGH, (float)ohlc[1]);
series.setFloat(i, Values.LOW, (float)ohlc[2]);
series.setFloat(i, Values.CLOSE, (float)ohlc[3]);

if (i == size - 1) {
info(String.format("LIVE UPDATE %s (%s Paris): O:%.4f H:%.4f L:%.4f C:%.4f %s",
targetSymbol, paris(now), ohlc[0], ohlc[1], ohlc[2], ohlc[3],
isUp ? "UP" : "DOWN"));
}
}

series.setComplete(i, false);
}
}

@Override
protected void calculate(int index, DataContext ctx) {
var series = ctx.getDataSeries();
if (series.isComplete(index)) return;

calculateValues(ctx);
}

@Override
public void destroy() {
String targetSymbol = getTargetSymbol();
info(String.format("CSV Data Live V3.0 destroy - %s: %d historical, %d real-time, %d OHLC cached",
targetSymbol != null ? targetSymbol : "N/A",
historicalCloses.size(), csvTimestampValues.size(), recentOhlcCache.size()));
historicalCloses.clear();
csvTimestampValues.clear();
recentOhlcCache.clear();
super.destroy();
}
}
 
? Contributing

Feel free to modify, improve, or adapt this code for your needs. If you make improvements, please share them with the community!

You're welcome to:
- Build and distribute a compiled JAR file for easier installation
- Fork and enhance the functionality
- Share your modifications in this thread
- Create derivative works with attribution

If you build a JAR, please share it here so others can benefit without needing to compile!

⚠️ Disclaimer

This is a free community contribution. Use at your own risk. Always test thoroughly before using in production. No warranty or support is provided.
 
? Beitragen

Sie können diesen Code gerne ändern, verbessern oder an Ihre Bedürfnisse anpassen. Wenn Sie Verbesserungen vornehmen, teilen Sie diese bitte mit der Community!

Gerne können Sie:
- Erstellen und verteilen Sie eine kompilierte JAR-Datei für eine einfachere Installation
- Fork und Erweiterung der Funktionalität
- Teilen Sie Ihre Änderungen in diesem Thread
- Erstellen Sie abgeleitete Werke mit Namensnennung

Wenn Sie ein JAR erstellen, geben Sie es bitte hier frei, damit andere davon profitieren können, ohne es kompilieren zu müssen!

⚠️ Haftungsausschluss

Dies ist ein kostenloser Beitrag der Community. Die Nutzung erfolgt auf eigene Gefahr. Vor dem produktiven Einsatz immer gründlich testen. Es wird keine Garantie oder Support gewährt.
Have you tested your indicator?
 
Top