CVD Indicator (Cumulative Volume Delta)
This example demonstrates a CVD (Cumulative Volume Delta) indicator that has been verified to match Bookmap's closed-source CVD indicator exactly.
What is CVD?โ
Cumulative Volume Delta tracks the running difference between buying and selling volume:
CVD = รยฃ (Buy Volume - Sell Volume)
Where:
- Buy Volume: Trades where the buyer was the aggressor (lifted the ask)
- Sell Volume: Trades where the seller was the aggressor (hit the bid)
CVD reveals the underlying buying/selling pressure that price alone doesn't show.
The Core Formulaโ
if (tradeInfo.isBidAggressor) {
cvdValue += size; // Aggressive buy รขโ โ positive delta
} else {
cvdValue -= size; // Aggressive sell รขโ โ negative delta
}
isBidAggressor | Market Action | CVD Impact |
|---|---|---|
true | Buyer lifted the ask | + size |
false | Seller hit the bid | - size |
Complete Implementationโ
package com.bookmap.addons.orderflow;
import velox.api.layer1.annotations.*;
import velox.api.layer1.data.InstrumentInfo;
import velox.api.layer1.data.TradeInfo;
import velox.api.layer1.messages.indicators.Layer1ApiUserMessageModifyIndicator.GraphType;
import velox.api.layer1.simplified.*;
import velox.gui.StrategyPanel;
import javax.swing.*;
import java.awt.*;
@Layer1SimpleAttachable
@Layer1StrategyName("CVD Indicator")
@Layer1ApiVersion(Layer1ApiVersionValue.VERSION2)
public class CvdIndicator implements CustomModule, TradeDataListener, CustomSettingsPanelProvider {
private IndicatorModifiable cvdIndicator;
private double cvdValue = 0;
private Color indicatorColor = Color.GREEN;
private int lineWidth = 2;
@Override
public void initialize(String alias, InstrumentInfo info, Api api, InitialState initialState) {
cvdIndicator = api.registerIndicatorModifiable("CVD", GraphType.BOTTOM);
cvdIndicator.setColor(indicatorColor);
cvdIndicator.setWidth(lineWidth);
}
@Override
public void onTrade(double price, int size, TradeInfo tradeInfo) {
if (tradeInfo.isBidAggressor) {
cvdValue += size; // Buying volume (bid aggressor)
} else {
cvdValue -= size; // Selling volume (ask aggressor)
}
cvdIndicator.addPoint(cvdValue);
}
@Override
public StrategyPanel[] getCustomSettingsPanels() {
StrategyPanel panel = new StrategyPanel("CVD Settings");
JButton colorButton = new JButton("Change Color");
colorButton.addActionListener(e -> {
Color newColor = JColorChooser.showDialog(null, "Choose CVD Line Color", indicatorColor);
if (newColor != null) {
indicatorColor = newColor;
cvdIndicator.setColor(indicatorColor);
}
});
panel.add(colorButton);
JSpinner widthSpinner = new JSpinner(new SpinnerNumberModel(lineWidth, 1, 5, 1));
widthSpinner.addChangeListener(e -> {
lineWidth = (int) widthSpinner.getValue();
cvdIndicator.setWidth(lineWidth);
});
panel.add(new JLabel("Line Width:"));
panel.add(widthSpinner);
return new StrategyPanel[] { panel };
}
@Override
public void stop() {
// Cleanup if necessary
}
}
Key Implementation Detailsโ
1. Indicator Registrationโ
cvdIndicator = api.registerIndicatorModifiable("CVD", GraphType.BOTTOM);
IndicatorModifiable: Allows dynamic color/width changes after registrationGraphType.BOTTOM: Places indicator in separate panel below price chart
2. No Price Conversion Neededโ
Unlike price-based indicators, CVD only uses:
size- Trade volume (already in correct units)tradeInfo.isBidAggressor- Aggressor side classification
No pips conversion required.
3. Zero-Size Tradesโ
Zero-size trades (MBO updates) naturally contribute nothing:
cvdValue += 0; // No impact
No explicit filtering needed, though you may add it for efficiency:
if (size == 0) return;
4. Settings Panelโ
CustomSettingsPanelProvider enables runtime customization:
- Color picker via
JColorChooser - Line width via
JSpinner
Verificationโ
This implementation has been verified against Bookmap's native CVD indicator:
| Indicator | Value |
|---|---|
| Custom (green) | -38.0 |
| Bookmap native | -38 |
Result: Exact match confirmed.
Use Casesโ
Trading Applicationsโ
| Pattern | Interpretation |
|---|---|
| Price up, CVD up | Confirmed bullish (buying pressure) |
| Price up, CVD down | Divergence - potential reversal |
| Price down, CVD down | Confirmed bearish (selling pressure) |
| Price down, CVD up | Divergence - potential reversal |
Extensionsโ
This foundation can be extended for:
- Session CVD - Reset at session boundaries
- Delta Bars - Per-bar delta instead of cumulative
- Delta Divergence Alerts - Automatic divergence detection
- Multi-timeframe Delta - Compare delta across timeframes
Session-Based CVD Variantโ
To reset CVD at session open:
private boolean isNewSession = false;
@Override
public void onTrade(double price, int size, TradeInfo tradeInfo) {
if (isNewSession) {
cvdValue = 0;
isNewSession = false;
}
if (tradeInfo.isBidAggressor) {
cvdValue += size;
} else {
cvdValue -= size;
}
cvdIndicator.addPoint(cvdValue);
}
Combine with HistoricalModeListener or time-based checks to detect session boundaries.
Data Source Compatibilityโ
| Data Source | CVD Accuracy |
|---|---|
| Rithmic (live) | รขลโฆ Exact |
| Rithmic (recorded) | รขลโฆ Exact |
| dxFeed Historical | รขลโฆ Accurate (sizes and aggressor flags preserved) |
Unlike volume profile, CVD is not affected by price aggregation since it only depends on size and aggressor side, both of which are preserved in aggregated data.
See Alsoโ
- Data Listeners - TradeDataListener interface
- TradeInfo - Trade information including
isBidAggressor - Api Interface - Registering indicators
- On Trade Example - Volume profile with trade data