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:
- Windows 10
- Java JDK 8
- Eclipse IDE or Intellij IDEA
- Eclipse plugin Buildship
- Git
- Git Extensions [TODO: why?]
- Gradle
- Bookmap [TODO: registration, license, installation]
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 correspondingCustomModuleAdapter
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 enabledBackfill
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 MBOHistorical
data is the same asReplay
data. It's stored in Bookmap's bmf files inC:/Bookmap/Feeds/
folder and can be replayed by Bookmap. These data files don't contain theBackfill
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: