Skip to main content

Bookmap Add-ons API usage guide for Simplified Framework

This tutorial covers most of the API topics for creating a fast and reliable Bookmap add-on with any level of business logic sophistication. The tutorial starts with installing the development environment and ends with an obfuscated and licensed add-on offered at the marketplace. It assumes only a basic knowledge of Java and some understanding of Market Mechanics. Advanced developers can skip it altogether or use just as a reference upon need. The tutorial also provides a framework with a set of tools which can help to design and implement a wide range of add-ons.

The result of this tutorial is an actual add-on [TODO: name] which is offered on the marketplace for free [TODO: link] and its entire source code is published [TODO: here].

Environment & Pre-requirements

Developers are not restricted to a particular development environment. Here we use the following arsenal of tools:

Build existing demo project

In order to validate the dev. environment, build a demo project from source code. Launch a command prompt window (in Windows either cmd.exe or PowerShell), navigate to an empty folder, and run:

git clone https://github.com/BookmapAPI/DemoStrategies.git
cd DemoStrategies/Strategies
gradle jar

This will create a jar file in ./build/libs/

Install the add-on to Bookmap

  • Launch Bookmap in either Realtime or Replay mode [TODO: give specific examples. BTC in realtime?]
  • Open the add-ons window by clicking on Configure API plugins button or via menu: Settings -> Api plugins configuration
  • Click Add.., select the jar file, then select the add-on you wish to load. Note that a single jar file may contain several add-ons (all can be added one by one)
  • Enable / Disable the add-on using corresponding checkbox.
  • Press the button near the checkbox. This brings to front add-on's settings panel (if implemented by the add-on). It also contains a "Remove" button which uninstalls the add-on

Assuming that the test was successful, let's build a new add-on from scratch. Otherwise [TODO: troubleshooting].

"Hello Bookmap" add-on in 2 minutes

Create a projects

Create a folder, for instance, C:\dev\bookmap-addons\bookmap-api-tutorial. Navigate into it with your command prompt window and run:

gradle init --type java-library

Hit Enter whenever asked to select project configuration options (we'll use the default options)

Import the projects into Eclipse

In Eclipse select File -> Import -> Gradle -> Existing Gradle Project, and use the wizard to select the location and to import the project.

For beginners: By default Gradle creates a sample java file Library.java and corresponding test file. Delete these files and the entire test package because we will not use them in this tutorial.

Import the projects into Intellij IDEA

If no project is currently opened in IntelliJ IDEA, click Open or Import on the welcome screen. Otherwise, select File | Open from the main menu. In the dialog that opens, select a directory containing a Gradle project and click OK. IntelliJ IDEA opens and syncs the project in the IDE.

Bookmap API dependencies

Find a file build.gradle in Eclipse or Intellij project's view and replace its entire content with the following:

build.gradle

apply plugin: 'java'
apply plugin: 'eclipse'

repositories {
mavenCentral()
maven {
url "https://maven.bookmap.com/maven2/releases/"
}
}

eclipse.classpath.downloadJavadoc = true

dependencies {
compileOnly group: 'com.bookmap.api', name: 'api-core', version: '7.1.0.35';
compileOnly group: 'com.bookmap.api', name: 'api-simplified', version: '7.1.0.35';
}

Advanced: If your add-on requires additional dependencies, include them according to the commented rules in this example:

build.gradle

dependencies {
// Use "compileOnly" for Bookmap API libraries and for any external library that is
// included in Bookmap runtime. Such libraries are located in C:\Program Files\Bookmap\lib
compileOnly group: 'com.bookmap.api', name: 'api-core', version: '7.1.0.35';
compileOnly group: 'com.bookmap.api', name: 'api-simplified', version: '7.1.0.35';

compileOnly group: 'com.google.code.gson', name: 'gson', version: '2.8.5';
compileOnly group: 'org.apache.commons', name: 'commons-lang3', version: '3.9'

// Use "implementation" for any other external dependency
implementation group: 'com.esotericsoftware', name: 'kryo', version: '5.0.0-RC4';

// If you wish to run the code from IDE without Bookmap, e.g. for test ot debug,
// mark all "compileOnly" dependencies as "runtime" also
runtime group: 'com.bookmap.api', name: 'api-core', version: '7.1.0.35';
runtime group: 'com.bookmap.api', name: 'api-simplified', version: '7.1.0.35';

runtime group: 'com.google.code.gson', name: 'gson', version: '2.8.5';
runtime group: 'org.apache.commons', name: 'commons-lang3', version: '3.9'
}

Now we are ready to create some add-ons

For beginners (Eclipse): After updating the build.gradle file, right-click on the project and click Gradle -> Refresh Gradle Project. Alternatively, configure automatic refresh / synchronization: clisk Windows -> Preferences, type "gradle" in the search field and select the checkbox Automatic....

For beginners (Intellij): After updating the build.gradle file, click Load gradle changes icon hovering over it's code, or press Ctrl+Shift+O keyboard shortcut.

"Hello Bookmap" Java code

The minimum necessary for a class to be a Bookmap add-on is:

  • to have the @Layer1SimpleAttachable and @Layer1ApiVersion annotations
  • to implement the CustomModule interface or corresponding CustomModuleAdapter adapter which has the default (empty) implementation.

HelloBookmapApiMinimum.java

@Layer1SimpleAttachable
@Layer1ApiVersion(Layer1ApiVersionValue.VERSION1)
public class HelloBookmapApiMinimum implements CustomModuleAdapter {
}

Let's also implement another Hello add-on which logs "Hello" and "Bye" during its enabling and disabling accordingly. Log is a built-in singleton accessible to add-ons.

HelloBookmapApiWithLogs.java

@Layer1SimpleAttachable
@Layer1StrategyName("Hello Bookmap API with logs")
@Layer1ApiVersion(Layer1ApiVersionValue.VERSION1)
public class HelloBookmapApiWithLogs implements CustomModule {

@Override
public void initialize(String alias, InstrumentInfo info, Api api, InitialState initialState) {
Log.info("Hello");
}

@Override
public void stop() {
Log.info("Bye");
}
}

Build

Use the same command prompt window (assuming you didn't close it) and run:

gradle jar

This creates a jar file \build\libs\bookmap-api-tutorial.jar which includes both of our Hello... add-ons.

Install the add-on to Bookmap

Refer to previous section and install the add-ons one by one. Notice that because HelloBookmapApiMinimum.java doesn't have @Layer1StrategyName annotation, Bookmap names it by its full pathname.

Enable / disable both add-ons using corresponding checkboxes. In order to remove an add-on, and then uninstall them using "Remove" button.

You can find the "Hello" and "Bye" log entries in the log file in C:\Bookmap\Logs or via File -> Show log file.

Debugging

[TODO: also remind to unload add-ons for editing / rebuilding]

Market data

Add-ons can subscribe to various types of market data simultaneously. Each type of market data is represented by a dedicated listener interface which determines the callbacks for that type of data. Each listener interface has corresponding adapter interface with default (empty) implementation of those callbacks, for example, TradesListener and TradesAdapter, TimeListener and TimeAdapter, CustomModule and CustomModuleAdapter, etc. Here we will cover most of the listener interfaces one by one:

Timestamp listener

Add-ons that implement TimeListener will receive the callback onTimestamp before any other update. The last provided timestamp is the true timestamp for all the following updates.

MarketDataListener.java

@Layer1SimpleAttachable
@Layer1StrategyName("Market Data Listener")
@Layer1ApiVersion(Layer1ApiVersionValue.VERSION1)
public class MarketDataListener implements CustomModuleAdapter, TimeListener {

private long timestamp; // holds the true timestamp of any other data event

@Override
public void onTimestamp(long nanoseconds) {
timestamp = nanoseconds;
}
}

Market by Price (MBP)

Subscribe to MBP data by implementing DepthDataListener. Here, we also use the MBP updates via onDepth callback and manage the Order Book.

MarketDataListener.java

@Layer1SimpleAttachable
@Layer1StrategyName("Market Data Listener")
@Layer1ApiVersion(Layer1ApiVersionValue.VERSION1)
public class MarketDataListener implements CustomModuleAdapter, DepthDataListener {

private final TreeMap<Integer, Integer> bids = new TreeMap<>(Comparator.reverseOrder());
private final TreeMap<Integer, Integer> asks = new TreeMap<>();

@Override
public void onDepth(boolean isBid, int price, int size) {
TreeMap<Integer, Integer> book = isBid ? bids : asks;
if (size == 0) {
book.remove(price);
} else {
book.put(price, size);
}
}
}

The Order Book consists of Bid and Ask collections of unique prices and corresponding size per price which is the sum of sizes of all Buy or Sell orders accordingly at that price. Both sides of the Order Book are sorted by price, but the Bid side is sorted in descending order. This allows to iterate over the bid and ask starting from the top of the book or to get the best price / size itself as following:

private void demoBestPriceSize() {
int bestBidPrice = bids.firstKey();
int bestAskPrice = asks.firstKey();
int bestBidSize = bids.firstEntry().getValue();
int bestAskSize = asks.firstEntry().getValue();
}

private int demoSumOfPriceLevels(boolean isBid, int numLevelsToSum) {
int sizeOfTopLevels = 0;
for (Integer size : (isBid ? bids : asks).values()) {
if (--numLevelsToSum < 0) {
break;
}
sizeOfTopLevels+= size;
}
return sizeOfTopLevels;
}

Depending on the exchange and the data vendor, some instruments may provide only a limited number of market depth levels (e.g. 10 or 20) instead of full market depth. Also, in general, Order Book is allowed to be empty, for instance outside trading hours, or just before the add-on received the initial snapshot, or during rebuilding of the Order Book.

Market by Order (MBO)

MBO data is a type of Order-by-Order data provided by CME exchange since 2017. There are multiple advantages of MBO over MBP, for instance it makes possible (not given) to identify Iceberg orders and executions of Stop orders. Multiple data vendors provide CME data, but currently (July 2020) only Rithmic provides the MBO data.

Add-ons can receive MBO data by implementing 3 callbacks of MarketByOrderDepthDataListener interface as following:

MarketDataListener.java

@Layer1SimpleAttachable
@Layer1StrategyName("Market Data Listener")
@Layer1ApiVersion(Layer1ApiVersionValue.VERSION1)
public class MarketDataListener implements CustomModuleAdapter, MarketByOrderDepthDataListener {

private final OrderBookMbo orderBook = new OrderBookMbo();

@Override
public void send(String orderId, boolean isBid, int price, int size) {
orderBook.send(orderId, isBid, price, size);
}

@Override
public void replace(String orderId, int price, int size) {
orderBook.replace(orderId, price, size);
}

@Override
public void cancel(String orderId) {
orderBook.cancel(orderId);
}
}

The implementation of the MBO Order Book is a little more complicated than MBP Order Book. Therefore the API exposes an implementation of such class: ◦velox.api.layer1.layers.utils.mbo.OrderBookMbo which is used in the above code.

However, if you need more control and access over the Order Book, you can implement it yourself. Here is an example of such class:

OrderBook.java

public class OrderBook implements MarketByOrderDepthDataListener {

public static class Order {

public String orderId;
public boolean isBid;
public int price;
public int size;

public Order(String orderId, boolean isBid, int price, int size) {
this.orderId = orderId;
this.isBid = isBid;
this.price = price;
this.size = size;
}
}

public final TreeMap<Integer, Map<String, Order>> asks = new TreeMap<>((a, b) -> a - b);
public final TreeMap<Integer, Map<String, Order>> bids = new TreeMap<>((a, b) -> b - a);
public final Map<String, Order> orders = new HashMap<>();

@Override
public void send(String orderId, boolean isBid, int price, int size) {
Order order = new Order(orderId, isBid, price, size);
orders.put(orderId, order);
(isBid ? bids : asks).computeIfAbsent(price, k -> new LinkedHashMap<>()).put(orderId, order);
}

@Override
public void replace(String orderId, int price, int size) {
Order order = orders.get(orderId);
if (price == order.price && size < order.size) {
order.size = size;
} else { // if order size increased, move the order to the end of the queue regardless of price change
cancel(orderId);
send(orderId, order.isBid, price, size);
}
}

@Override
public void cancel(String orderId) {
Order order = orders.remove(orderId);
Map<String, Order> priceLevel = (order.isBid ? bids : asks).get(order.price);
priceLevel.remove(orderId);
if (priceLevel.isEmpty()) {
(order.isBid ? bids : asks).remove(order.price);
}
}
}

Similar to MBP, here you can iterate over bid and ask sides of the Order Book starting from the top of the book. The difference is that each price level isn't just the total size of its orders, but a queue of individual orders stored in LinkedHashMap. This allows to iterate over the orders queue like using a List, i.e. according to orders' insertion order (as it supposed to be according to Price-Time Priority Matching Algorithm), but also to find and remove an order from the queue almost as fast as using a HashMap.

This MBO Order Book can process ~4 million updates per second on a regular laptop. Considering that even most active instruments such as ES futures have 5-10 million updates per day, this implementation is fast enough. But it isn't the fastest implementation. You are welcome to challenge yourself by improving its performance.

Tip: You can compute the total size at a price level as following:

public int getTotalSizeAtPrice(boolean isBid, int price) {
return (isBid ? bids : asks).get(price).values().stream().mapToInt(order -> order.size).sum();
}

However, if you need to access such method frequently, consider wrapping the price level map of orders into another class e.g. PriceLevel which caches the total size when any of the orders is updated.

Note that MBO data can be converted into MBP, but not vice versa. Therefore, when Bookmap is connected to Rithmic CME data (which is MBO), add-ons can subscribe to either MBO or MBP data or both (the MBP data is generated by Bookmap from MBO). But when Bookmap is connected to MBP data, add-ons don't receive any data via the MarketByOrderDepthDataListener callbacks.

Direct Market data subscription

You have probably noticed that the above OrderBook.java class itself implements MarketByOrderDepthDataListener and the add-on acts just as a proxy which looks awkward. Indeed, there is another way to feed the order book object with MBO data:

MarketDataListener.java

@NoAutosubscription
@Layer1SimpleAttachable
@Layer1StrategyName("Market Data Listener")
@Layer1ApiVersion(Layer1ApiVersionValue.VERSION1)
public class MarketDataListener implements CustomModuleAdapter {

private final OrderBook orderBook = new OrderBook();

@Override
public void initialize(String alias, InstrumentInfo info, Api api, InitialState initialState) {
api.addMarketByOrderDepthDataListeners(orderBook);
}
}

Notice such subscription method requires additional annotation: @NoAutosubscription

TradesListener

To make this example a little bit practical, let's create a class that computes VWAP (volume weighted average price):

Vwap.java

public class Vwap {

public double priceSize = 0;
public long volume = 0;

public void onTrade(double price, int size) {
priceSize += price * size;
volume += size;
}
}

And here is an add-on which implements TradeDataListener and prints the VWAP statistics at the end of the session:

MarketDataListener.java

@Layer1SimpleAttachable
@Layer1StrategyName("Market Data Listener")
@Layer1ApiVersion(Layer1ApiVersionValue.VERSION1)
public class MarketDataListener implements CustomModuleAdapter, TradeDataListener {

private final Vwap buyers = new Vwap();
private final Vwap sellers = new Vwap();
private double minPriceIncrement;

@Override
public void initialize(String alias, InstrumentInfo info, Api api, InitialState initialState) {
minPriceIncrement = info.pips;
}

@Override
public void onTrade(double price, int size, TradeInfo tradeInfo) {
(tradeInfo.isBidAggressor ? buyers : sellers).onTrade(price, size);
}

@Override
public void stop() {
double vwapBuy = minPriceIncrement * buyers.priceSize / buyers.volume;
double vwapSell = minPriceIncrement * sellers.priceSize / sellers.volume;
double vwap = minPriceIncrement * (buyers.priceSize + sellers.priceSize) / (buyers.volume + sellers.volume);
Log.info(String.format("VWAP Buy: %.2f, Sell: %.2f, Total: %.2f", vwapBuy, vwapSell, vwap));
}
}

Pre-subscription Market Data

Suppose, that you launched Bookmap at the beginning of the trading day, but you didn't enable the above VWAP add-on. Simply add HistoricalDataListener to its list of interfaces and when you enable the add-on, it will receive all the data since instrument subscription. This interface doesn't have any callbacks:

MarketDataListener.java


@Layer1SimpleAttachable
@Layer1StrategyName("Market Data Listener")
@Layer1ApiVersion(Layer1ApiVersionValue.VERSION1)
public class MarketDataListener implements CustomModuleAdapter, TradeDataListener, HistoricalDataListener {

private final Vwap buyers = new Vwap();
private final Vwap sellers = new Vwap();
private double minPriceIncrement;

@Override
public void initialize(String alias, InstrumentInfo info, Api api, InitialState initialState) {
minPriceIncrement = info.pips;
}

@Override
public void onTrade(double price, int size, TradeInfo tradeInfo) {
(tradeInfo.isBidAggressor ? buyers : sellers).onTrade(price, size);
}

@Override
public void stop() {
double vwapBuy = minPriceIncrement * buyers.priceSize / buyers.volume;
double vwapSell = minPriceIncrement * sellers.priceSize / sellers.volume;
double vwap = minPriceIncrement * (buyers.priceSize + sellers.priceSize) / (buyers.volume + sellers.volume);
Log.info(String.format("VWAP Buy: %.2f, Sell: %.2f, Total: %.2f", vwapBuy, vwapSell, vwap));
}
}

Note that in order to measure the VWAP "till now", you can enable it, wait several seconds until it received all the data, and then disable it.

The add-on can also get notification when the pre-subscription data is processed and the real-time data starts. For this purpose use HistoricalModeListener instead of HistoricalDataListener and implement onRealtimeStart callback:

MarketDataListener.java

@Layer1SimpleAttachable
@Layer1StrategyName("Market Data Listener")
@Layer1ApiVersion(Layer1ApiVersionValue.VERSION1)
public class MarketDataListener implements CustomModuleAdapter, TradeDataListener, HistoricalModeListener {

private final Vwap buyers = new Vwap();
private final Vwap sellers = new Vwap();
private double minPriceIncrement;
private Api api;

@Override
public void initialize(String alias, InstrumentInfo info, Api api, InitialState initialState) {
this.api = api;
minPriceIncrement = info.pips;
}

@Override
public void onTrade(double price, int size, TradeInfo tradeInfo) {
(tradeInfo.isBidAggressor ? buyers : sellers).onTrade(price, size);
}

@Override
public void stop() {
double vwapBuy = minPriceIncrement * buyers.priceSize / buyers.volume;
double vwapSell = minPriceIncrement * sellers.priceSize / sellers.volume;
double vwap = minPriceIncrement * (buyers.priceSize + sellers.priceSize) / (buyers.volume + sellers.volume);
Log.info(String.format("VWAP Buy: %.2f, Sell: %.2f, Total: %.2f", vwapBuy, vwapSell, vwap));
}

@Override
public void onRealtimeStart() {
Log.info("Real-time data started. Unloading...");
api.unload();
}
}

In this case simple case the add-on unloads itself using api.unload() which triggers the stop() callback which prints the needed information. In general case the HistoricalModeListener interface is used to do something before continuing with real-time data (for instance, to switch from indicator.addPoint(timestamp, value)to indicator.addPoint(value) which will be covered later).

To avoid confusions, there are 3 types of historical data:

  • Pre-subscription data is the data that was collected by Bookmap from the real-time data before the add-on was enabled
  • Backfill data is the data of up to 48 hours which precedes the real-time data. Typically, it's fetched by Bookmap from its own servers and it may not be MBO even if the real-time data is MBO
  • Historical data is the same as Replay data. It's stored in Bookmap's bmf files in C:/Bookmap/Feeds/ folder and can be replayed by Bookmap. These data files don't contain the Backfill data, but only the recorded real-time data.

Fixed Time Intervals

Sometimes the add-on needs to do some computation or update its indicator, but we don't want it to do it too frequently (e.g. every MBO update) from the performance perspective (there may be many thousands of MBO updates per second). For this purpose the add-ons can implement IntervalListener. Just remember that all timestamps in Bookmap are in nanoseconds.

MarketDataListener.java

@Layer1SimpleAttachable
@Layer1StrategyName("Market Data Listener")
@Layer1ApiVersion(Layer1ApiVersionValue.VERSION1)
public class MarketDataListener implements CustomModuleAdapter, IntervalListener {

@Override
public long getInterval() {
return Intervals.INTERVAL_100_MILLISECONDS;
}

@Override
public void onInterval() {
// do something every 100 milliseconds
}
}

Other data types

Other data types are less important because they are redundant, i.e. can be easily computed given the above data types.

BBO

In order to receive BBO (Best Bid and Offer / Ask), the add-on must implement BboListener interface which includes a single callback onBbo, called whenever any of the supplied arguments has changed.

@Layer1SimpleAttachable
@Layer1StrategyName("Market Data Listener")
@Layer1ApiVersion(Layer1ApiVersionValue.VERSION1)
public class MarketDataListener implements CustomModuleAdapter, BboListener {

@Override
public void onBbo(int bidPrice, int bidSize, int askPrice, int askSize) {
// Auto-generated method stub
}
}

Bars (Candlesticks)

Bars represent information about traded volume, aggregated over fixed time interval. Therefore the BarDataListener includes getInterval method, which is called by the API just once. The onBar callback also provides instrument's MBP OrderBook for convenience.

@Layer1SimpleAttachable
@Layer1StrategyName("Market Data Listener")
@Layer1ApiVersion(Layer1ApiVersionValue.VERSION1)
public class MarketDataListener implements CustomModuleAdapter, BarDataListener {

@Override
public long getInterval() {
return Intervals.INTERVAL_1_SECOND; // time is always in nanoseconds
}

@Override
public void onBar(OrderBook orderBookMbp, Bar bar) {
// Auto-generated method stub
}
}

For beginners: Ctrl+Space opens a list of available object's methods as shown here.

Exercise

Remove @Override annotation from the onBar method and without implementing BarDataListener call it with its expected arguments every 100 milliseconds.



// _Scroll for a solution_


OrderBookMbp.java (let's make it a separate class)

public class OrderBookMbp implements DepthDataListener {

TreeMap<Integer, Integer> bids = new TreeMap<>(Comparator.reverseOrder());
TreeMap<Integer, Integer> asks = new TreeMap<>();

public void onDepth(boolean isBid, int price, int size) {
TreeMap<Integer, Integer> book = isBid ? bids : asks;
Integer dummy = (size == 0) ? book.remove(price) : book.put(price, size);
}
}

ExerciseCustomOnBar.java, the solution

@Layer1SimpleAttachable
@Layer1StrategyName("Exercise: custom onBar")
@Layer1ApiVersion(Layer1ApiVersionValue.VERSION1)
public class ExerciseCustomOnBar implements CustomModuleAdapter, IntervalListener, TradeDataListener, DepthDataListener {

private final OrderBookMbp orderBook = new OrderBookMbp();
private final Bar bar = new Bar();

private void onBar(OrderBookMbp orderBookMbp, Bar bar) {
// called as BarDataListener#onBar()
}

@Override
public void onInterval() {
onBar(orderBook, bar); // Call onBar()
bar.startNext(); // reset (rollover) the bar
}

@Override
public void onTrade(double price, int size, TradeInfo tradeInfo) {
bar.addTrade(tradeInfo.isBidAggressor, size, price);
}

@Override
public void onDepth(boolean isBid, int price, int size) {
orderBook.onDepth(isBid, price, size);
}

@Override
public long getInterval() {
return Intervals.INTERVAL_100_MILLISECONDS;
}
}

Exercise

Make the solution from the previous exercise more compact using the @NoAutosubscription annotation and corresponding data subscription method



// _Scroll for a solution_


This solution uses the same OrderBookMbp.java class and provides it as a DepthDataListener. If lambda expressions don't scare you, you might this approach more efficient in some cases.

@NoAutosubscription
@Layer1SimpleAttachable
@Layer1StrategyName("Exercise: custom onBar")
@Layer1ApiVersion(Layer1ApiVersionValue.VERSION1)
public class ExerciseCustomOnBar implements CustomModuleAdapter {

private final OrderBookMbp orderBook = new OrderBookMbp();
private final Bar bar = new Bar();

private onBar = (orderBookMbp, bar) -> {
}

@Override
public void initialize(String alias, InstrumentInfo info, Api api, InitialState initialState) {
api.addDepthDataListeners(orderBook);
api.addTradeDataListeners((price, size, tradeInfo) -> bar.addTrade(tradeInfo.isBidAggressor, size, price));
api.addIntervalListeners(new IntervalListener() {
@Override
public void onInterval() {
onBar(orderBook, bar); // Call onBar()
bar.startNext(); // Begin next interval's OPEN with current CLOSE
}
@Override
public long getInterval() {
return Intervals.INTERVAL_100_MILLISECONDS;
}
});
}
}

Rendering Indicators

Here is a simple example. This indicator computes VWAP during intervals of 10 seconds and displays the results at the end of each interval.

@Layer1SimpleAttachable
@Layer1StrategyName("Vwap Indicator")
@Layer1ApiVersion(Layer1ApiVersionValue.VERSION1)
public class VwapRendererWithBar implements CustomModuleAdapter, BarDataListener, HistoricalDataListener {

private Indicator vwapLine;

@Override
public void initialize(String alias, InstrumentInfo info, Api api, InitialState initialState) {
vwapLine = api.registerIndicator("VWAP", GraphType.PRIMARY, Double.NaN);
}

@Override
public long getInterval() {
return Intervals.INTERVAL_10_SECONDS;
}

@Override
public void onBar(OrderBook orderBook, Bar bar) {
vwapLine.addPoint(bar.getVwap());
}
}

Notice the HistoricalDataListener. It tells Bookmap to provide the data since instrument subscription, so we can see the VWAP line immediately after enabling the add-on, see the white line:

Axis rules

Axis groups

Computing and updating indicators

Performance

Settings

Settings declared by annotations

Settings stored by API

GUI (User interface)

EDT

Components and panels

Callbacks

Detached windows

Custom widgets

Historical data

Reloading the add-on

Custom data storage

Concurrency

Using L1 core API

Production

Licensing your add-on

Obfuscating your add-on

Proguard

Gradle configuration

Bookmap marketplace

Units of price, size, and time

Timestamp

Prices

Order size, volume, and quantities

API Javadoc

Advanced topics

Custom rendering on the chart