Extend BM DOM

Custom indicators, trading strategies, data export and recording and more...
blk
Posts: 44
Joined: Fri Nov 01, 2019 8:59 pm
Has thanked: 7 times

Extend BM DOM

Post by blk » Mon Apr 06, 2020 1:19 am

Hi,

Is there a way to extend BM DOM ? I would like to add extra columns and populate it via api.

If this is not possible, is this something you have on roadmap?

Thanks,

Andry API support
Posts: 548
Joined: Mon Jul 09, 2018 11:18 am
Has thanked: 25 times
Been thanked: 85 times

Re: Extend BM DOM

Post by Andry API support » Wed Apr 08, 2020 11:01 am

Currently there's no way to populate columns via API. The only way to automate populating columns (Cloud Notes type columns) is to create a server which can report (for any column cell) text, color text and background color. The least refresh interval that can be set from gui is 1 min which is sometimes too long but a server can override it. Let me know if you're interested in code examples. If not, I'll move the topic to Feature Requests.

blk
Posts: 44
Joined: Fri Nov 01, 2019 8:59 pm
Has thanked: 7 times

Re: Extend BM DOM

Post by blk » Wed Apr 08, 2020 12:53 pm

Hi Andrey,

Can you show me an example of how to override the 1 min refresh interval for cloud notes?

You can move Adding Custom DOM columns and populating via API to features request.

Thanks

Andry API support
Posts: 548
Joined: Mon Jul 09, 2018 11:18 am
Has thanked: 25 times
Been thanked: 85 times

Re: Extend BM DOM

Post by Andry API support » Thu Apr 09, 2020 9:30 am

Hi blk,
this should work for latest 7.1 releases.

Cloud notes download url (change the port number if you need to):

Code: Select all

http://127.0.0.1:8089

notes server example (the field responsible to store a refresh interval is refreshDelayMillis):

Code: Select all

 
package layer1modules;

import java.awt.Color;
import java.io.BufferedOutputStream;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.io.PrintStream;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.Date;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.atomic.AtomicBoolean;

import velox.api.layer1.Layer1ApiProvider;
import velox.api.layer1.annotations.Layer1ApiVersion;
import velox.api.layer1.annotations.Layer1ApiVersionValue;
import velox.api.layer1.annotations.Layer1SimpleAttachable;
import velox.api.layer1.annotations.Layer1StrategyName;
import velox.api.layer1.common.Log;
import velox.api.layer1.data.InstrumentInfo;
import velox.api.layer1.data.TradeInfo;
import velox.api.layer1.layers.utils.OrderBook;
import velox.api.layer1.simplified.Api;
import velox.api.layer1.simplified.CustomModule;
import velox.api.layer1.simplified.DepthDataListener;
import velox.api.layer1.simplified.Indicator;
import velox.api.layer1.simplified.InitialState;
import velox.api.layer1.simplified.TradeDataListener;

@Layer1SimpleAttachable
@Layer1StrategyName("notes server 8089")
@Layer1ApiVersion(Layer1ApiVersionValue.VERSION2)
public class SimplifiedNotesServer8089 implements CustomModule, TradeDataListener, DepthDataListener
{
    protected int port = 8089;
    
    protected Indicator lastTradeIndicator;
    
    protected static final String NOTES_REFRESH_HEADER_NAME = "X-Bookmap-Cloud-Notes-Refresh-Delay-Millis";
    protected static final String header = "\"Symbol\",\"Price Level\",\"Note\",\"Foreground Color\",\"Background Color\","
            + "\"Text Alignment\",\"Notification Enabled\",\"Sound Notification Enabled\",\"Notification Is Repeatable\","
            + "\"Delay Before Repeating\",\"Subscribing Offset\",\"Notification Sound\"\r\n";
    
    protected static final String newLine="\r\n";
    
    @SuppressWarnings("unused")
    protected Layer1ApiProvider provider;
    protected OrderBook orderBook = new OrderBook();
    
    protected int refreshDelayMillis = 500;
    protected String alias;
    protected double pips;
    protected int prevBestBid;
    
    protected AtomicBoolean enabled = new AtomicBoolean(true);
    protected ServerSocket serverSocket;
    protected final ExecutorService readNotesExecutor = Executors.newSingleThreadExecutor();
    protected final ExecutorService incomingBboExecutor = Executors.newSingleThreadExecutor();
    protected NoteUnit actualBbo;
    protected final Object unitLock = new Object();
    
    volatile boolean isShuttingDown;
    
    protected int count;

    @Override
    public void initialize(String alias, InstrumentInfo info, Api api, InitialState initialState) {
        this.orderBook = new OrderBook();
        this.pips = info.pips;
        this.alias = alias;
        
        startServer();
    }
    
    @Override
    public void stop() {
        
        Thread th = new Thread(() -> {
            isShuttingDown = true;
            incomingBboExecutor.shutdownNow();
            enabled.set(false);
            try {
                serverSocket.close();
            } catch (Exception e) {
                e.printStackTrace();
            }
            readNotesExecutor.shutdownNow();
        });
        th.start();
    }

    @Override
    public void onTrade(double price, int size, TradeInfo tradeInfo) {

    }

    @Override
    public void onDepth(boolean isBid, int price, int size) {
        if (alias.equals(this.alias)) {
            orderBook.onUpdate(isBid, price, size);
            int bestBid = orderBook.getBestBidPriceOrNone();

            if (bestBid != Integer.MAX_VALUE && bestBid != prevBestBid) {
                onBestBid(bestBid, (int) orderBook.getSizeFor(true, bestBid));
            }
        }
        
    }
    public void onBestBid(int bidPrice, int bidSize) {
        if (bidPrice == 0) return;
        
        Runnable runnable = () -> {
            NoteUnit unit = new NoteUnit();
            unit.setSymbol(alias);
            unit.setPriceLevel((double)(bidPrice * pips));
            unit.setForegroundColor(Color.decode("#ffffff"));
            unit.setBackgroundColor(Color.decode("#00cc7a"));

            synchronized (unitLock) {
                actualBbo = unit;
            }
        };
        if (!isShuttingDown) {
            incomingBboExecutor.execute(runnable);
        }
    }

    protected void startServer() {
        Thread server = new Thread(() -> {
            try (ServerSocket socket = new ServerSocket(port)) {
                serverSocket = socket;

                while (enabled.get()) {
                    Socket connection = socket.accept();

                    try {
                        BufferedReader in = new BufferedReader(new InputStreamReader(connection.getInputStream()));
                        // read first line of request
                        String request = in.readLine();
                        if (request == null) {
                            continue;
                        }

                        Runnable runnable = () -> {
                            try {
                                OutputStream out = new BufferedOutputStream(connection.getOutputStream());

                                if (request.startsWith("GET")) {
                                    StringBuilder sb = new StringBuilder();
                                    sb.append(header);

                                    synchronized (unitLock) {
                                        if (actualBbo != null) {
                                            actualBbo.setNote(count++ + " Bbo is " + String.valueOf(actualBbo.getPriceLevel()));
                                            sb.append(actualBbo.getCsvLine());
                                            for (int i = -5; i < 6; i++) {
                                                if (i == 0) continue;
                                                NoteUnit unit = new NoteUnit();
                                                unit.setSymbol(alias);
                                                unit.setPriceLevel(actualBbo.getPriceLevel() + (double)i * pips);
                                                unit.setNote(String.valueOf(unit.getPriceLevel()));
                                                unit.setForegroundColor(Color.decode("#ffffff"));
                                                if (i < 0) {
                                                    unit.setBackgroundColor(Color.decode("#00cc7a"));
                                                } else {
                                                    unit.setBackgroundColor(Color.decode("#cc3300"));
                                                }
                                                sb.append(unit.getCsvLine());
                                            }
                                            
                                        } else {
                                            NoteUnit blankUnit = new NoteUnit();
                                            blankUnit.setSymbol(alias);
                                            blankUnit.setPriceLevel(0.0);
                                            sb.append(blankUnit.getCsvLine());
                                        }
                                    }

                                    String response = sb.toString();

//                                    Log.info(response);
                                    PrintStream printStream = new PrintStream(out);
                                    printStream.print("HTTP/1.0 200 OK" + newLine + "Content-Type: text/plain" + newLine
                                            + "Date: " + new Date() + newLine
                                            + NOTES_REFRESH_HEADER_NAME + ": " + String.valueOf(refreshDelayMillis) + newLine 
                                            + "Content-length: "
                                            + response.length() + newLine + newLine + response);
//                                    Log.info("count is " + (count -1));
                                    printStream.close();
                                    
                                }
                            } catch (IOException e) {
                                e.printStackTrace();
                            }
                        };
                        if (!isShuttingDown) {
                            readNotesExecutor.execute(runnable);
                        }
                    } catch (Exception e) {
                        Log.info("Exception0 for alias " + alias + "", e);
                    }
                }
            } catch (Exception e) {
                Log.info("Exception1 for alias " + alias + "", e);
            }
        });
        server.start();
    }
}

an auxiliary class:

Code: Select all

 
package layer1modules;

import java.awt.Color;
import java.io.File;
import java.lang.reflect.Field;

public class NoteUnit {
    enum TextAlignment {
        right, left, center;
    }

    private String symbol;
    private Double priceLevel;
    private String note;
    private Color foregroundColor;
    private Color backgroundColor;
    private TextAlignment alignment;
    private Boolean notificationEnabled;
    private Boolean soundNotificationEnabled;
    private Boolean notificationIsRepeatable;
    private Integer delayBeforeRepeating;
    private Integer subscribingOffset;
    private File notificationSound;
    private boolean isNew;
    
    public String getSymbol() {
        return symbol;
    }

    public void setSymbol(String symbol) {
        this.symbol = symbol;
    }

    public Double getPriceLevel() {
        return priceLevel;
    }

    public void setPriceLevel(Double priceLevel) {
        this.priceLevel = priceLevel;
    }

    public String getNote() {
        return note;
    }

    public void setNote(String note) {
        this.note = note;
    }

    public Color getForegroundColor() {
        return foregroundColor;
    }

    public void setForegroundColor(Color foregroundColor) {
        this.foregroundColor = foregroundColor;
    }

    public Color getBackgroundColor() {
        return backgroundColor;
    }

    public void setBackgroundColor(Color backgroundColor) {
        this.backgroundColor = backgroundColor;
    }

    public TextAlignment getAlignment() {
        return alignment;
    }

    public void setAlignment(TextAlignment alignment) {
        this.alignment = alignment;
    }

    public Boolean getNotificationEnabled() {
        return notificationEnabled;
    }

    public void setNotificationEnabled(Boolean notificationEnabled) {
        this.notificationEnabled = notificationEnabled;
    }

    public Boolean getSoundNotificationEnabled() {
        return soundNotificationEnabled;
    }

    public void setSoundNotificationEnabled(Boolean soundNotificationEnabled) {
        this.soundNotificationEnabled = soundNotificationEnabled;
    }

    public Boolean getNotificationIsRepeatable() {
        return notificationIsRepeatable;
    }

    public void setNotificationIsRepeatable(Boolean notificationIsRepeatable) {
        this.notificationIsRepeatable = notificationIsRepeatable;
    }

    public Integer getDelayBeforeRepeating() {
        return delayBeforeRepeating;
    }

    public void setDelayBeforeRepeating(Integer delayBeforeRepeating) {
        this.delayBeforeRepeating = delayBeforeRepeating;
    }

    public Integer getSubscribingOffset() {
        return subscribingOffset;
    }

    public void setSubscribingOffset(Integer subscribingOffset) {
        this.subscribingOffset = subscribingOffset;
    }

    public File getNotificationSound() {
        return notificationSound;
    }

    public void setNotificationSound(File notificationSound) {
        this.notificationSound = notificationSound;
    }

    public boolean isNew() {
        return isNew;
    }

    public void setNew(boolean isNew) {
        this.isNew = isNew;
    }

    public String getCsvLine() {
        Field[] fields = getClass().getDeclaredFields();
        StringBuilder sb = new StringBuilder();
        
        for (Field field : fields) {
            if (field.getName().equals("isNew")) continue;
            
            sb.append("\"");
            try {
                Object object = field.get(this);
                String objectAsText;
                
                if (object instanceof Color) {
                    Color color = (Color) object;
                    objectAsText = String.format("#%02x%02x%02x", color.getRed(), color.getGreen(), color.getBlue());  
                } else {
                    objectAsText = object == null ? "" : String.valueOf(object);
                }
                sb.append(objectAsText);
            } catch (IllegalArgumentException | IllegalAccessException e) {
                e.printStackTrace();
            }
            sb.append("\",");
        }
        sb.deleteCharAt(sb.length() - 1);
        sb.append("\n");
        return sb.toString();
    }
}

build.gradle Bookmap libs dependencies(change according to your Bookmap release number):

Code: Select all

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

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

blk
Posts: 44
Joined: Fri Nov 01, 2019 8:59 pm
Has thanked: 7 times

Re: Extend BM DOM

Post by blk » Thu Apr 09, 2020 12:49 pm

Thanks Andrey, appreciate your reply and code samples 👍 !

Post Reply