Class Layer1ApiSoundAlertMessage
- All Implemented Interfaces:
Layer1ApiStrategiesEchoMessagesLayer.StrategyEchoMessageFromLayer
public class Layer1ApiSoundAlertMessage extends java.lang.Object implements Layer1ApiStrategiesEchoMessagesLayer.StrategyEchoMessageFromLayer
Notification system examples
If you want to jump right into examples, take a look at DemoStrategies project. Here is a brief explanation of what you can find:
velox.api.layer1.simpledemo.alerts.tradeprice.SimplePriceAlertDemo- a basic demo that shows how you can create and send a single alert. Its creation is described step-by-step below.velox.api.layer1.simpledemo.alerts.simplegui.SimpleAlertGuiDemo- a "Hello-World" style demo showing how to incorporate your own GUI for managing notifications. Implemented step-by-step inLayer1ApiAlertGuiMessagejavadoc.velox.api.layer1.simpledemo.alerts.tradeprice.CustomPriceAlertDemo- a more elaborate example of the notification system usage. Shows how an addon can create alerts dynamically, using its own GUI.velox.api.layer1.simpledemo.alerts.manual.Layer1ApiAlertDemo- "synthetic" example, allowing you to see as many notification systems as possible. Alerts are sent manually from the strategies dialog.
Notification system step-by-step guide
The notification system has multiple moving parts and might seem intimidating at first glance. Below is a step-by-step guide to creating a simple addon that sends alerts based on some market event.
First, let's define the framework of working with the notification system.
Just like in many other parts of Bookmap, an addon communicates with the Bookmap
via special messages objects. That is, your addon should listen for messages via
Layer1ApiAdminListener.onUserMessage(Object) and
send them with Layer1ApiAdminProvider.sendUserMessage(Object)
Notification system workflow
The workflow looks like this:
- Register a UI for controlling addon's notifications with
Layer1ApiAlertGuiMessage(optional step, and we will omit it at first in the example) - Declare a group of sound alerts by sending
Layer1ApiSoundAlertDeclarationMessage, store this message to later link it to actual declarations - Listen for declarations messages with a flag isAdd = false
- Send an initial
Layer1ApiAlertSettingsMessagemessage for a new declaration, and listen for alert settings coming from Bookmap - When a trigger, defined internally by the addon occurs - send an actual alert with
Layer1ApiSoundAlertMessage - (optional) stop alert with
Layer1ApiSoundAlertCancelMessage- if an alert is repeated, or has sound notification.
Example of an addon leveraging notification system
Now, lets see how it all works together in a more real example
Lets say, that we want to develop an addon that tracks the trades, and if there is a trade with price larger than 10 - we notify a user.
First, we should create an entrypoint class, so it can be loaded by the Bookmap (this is the same process as for any other addon).
@Layer1Attachable@Layer1StrategyName("Price alert demo")@Layer1ApiVersion(Layer1ApiVersionValue.VERSION2)public class SimplePriceAlertDemo implements Layer1ApiAdminAdapter, Layer1ApiDataAdapter, Layer1ApiFinishable { private Layer1ApiProvider provider; public SimplePriceAlertDemo(Layer1ApiProvider provider) { this.provider = provider; ListenableHelper.addListeners(provider, this); }@Overridepublic void finish() { } }
Now, our addon can be loaded, and it also gets injected the Layer1ApiProvider -
the key entity in Bookmap API, which gives access to various types of events
(note the subscription with ListenableHelper).
Later we will also use the provider to send messages to BM. In addition, we need
to implement Layer1ApiFinishable, but we don't need to add any implementation
for now
Also, our class implements Layer1ApiAdminAdapter - which gives us ability
to listen for messages coming from BM by implementing
Layer1ApiAdminAdapter.onUserMessage(Object)
In addition, we want to listen for trades, an for that we implement Layer1ApiDataAdapter,
as its method Layer1ApiDataAdapter.onTrade(String, double, int, TradeInfo)
does exactly what we want.
Next step - lets listen for the trades, and start simple - log into console a message, when the trade with price > 10 occurred:
@Override
public void onTrade(String alias, double price, int size, TradeInfo tradeInfo) {
if (price > 10) {
// Here, instead of printing to console, we later want to send an alert
System.out.printf("Trade of price > 10 occurred, actual price={%f}, size={%d}%n", price, size);
}
}
Simple, isn't it? Now, instead of plain logging, we want to use the notification system. Lets go through its workflow:
- Register a UI for controlling addon's notifications with
Layer1ApiAlertGuiMessage
To keep this example simple, we won't implement GUI for it. For an example
of an addon that creates its GUI - take a look at Layer1ApiAlertGuiMessage javadoc
- Declare a group of sound alerts by sending
Layer1ApiSoundAlertDeclarationMessage, store this message to later link it to actual declarations
Lets think about our alert - how we want our user to see it. For this simple example, our alert will show popup, won't have sound notification and will be a single-shot (non-repeated) alert. It will be linked to all available instruments aliases. A description for the trigger of this alert might be simply - "Trade price > 10"
With this description in mind lets create a declaration message and send it from the class constructor. Also, we want to store the created declaration for later use as a class field:
private Layer1ApiSoundAlertDeclarationMessage declarationMessage;
public SimplePriceAlertDemo(Layer1ApiProvider provider) {
this.provider = provider;
ListenableHelper.addListeners(provider, this);
declarationMessage = Layer1ApiSoundAlertDeclarationMessage.builder()
.setTriggerDescription("Trade price > 10")
.setSource(SimplePriceAlertDemo.class)
.setPopupAllowed(true)
.setAliasMatcher(Layer1ApiSoundAlertDeclarationMessage.ALIAS_MATCH_ALL)
.build();
provider.sendUserMessage(declarationMessage);
}
If we did everything right - in Bookmap a new record should appear in the "Configure alerts" table - available via File -> Alerts -> Configure alerts. We WON'T see the notifications just yet, we are still setting them up.
Next step in the notifications workflow is greatly connected to this one:
- Listen for declarations messages with a flag isAdd = false
The idea is - when your addon sees this type of declaration message - it should stop sending notifications of that type. Your addon gets this types of notifications when a user clicks on "Remove alert" button on Configure alerts table mentioned above. Thus, lets setup listening for this type of message:
@Override
public void onUserMessage(Object data) {
Layer1ApiSoundAlertDeclarationMessage obtainedDeclarationMessage = (Layer1ApiSoundAlertDeclarationMessage) data;
if (obtainedDeclarationMessage.source == SimplePriceAlertDemo.class && !obtainedDeclarationMessage.isAdd) {
declarationMessage = null;
}
}
Note: Here we have only one declaration message, while in the real application you might have multiple - in that case you can store them in a Map using id as a key, and when a message with the same id arrives - remove the value from the Map. However, the main idea of this guide is to introduce concepts one-by-one, and use as little non-necessary functionality as possible, so we won't be bothering with the multiple declarations just yet.
As you might have noticed, we are not using the value of the declaration message - and it will be fixed soon.
For now, lets go to the next step in the notifications workflow:
- Send an initial
Layer1ApiAlertSettingsMessagemessage for a new declaration, and listen for alert settings coming from Bookmap
The settings message is connected to a declaration message, and it can be used to notify your addon about settings changes, or, vice versa - your addon can notify Bookmap about the settings changes. Now, before we send the actual notifications, lets define the settings that will be used in the alert message, and send them to Bookmap. Also, we want to setup listening for the settings messages, which will look just like the listening for the declarations messages in the previous step. Below is the result of code changes:
private Layer1ApiAlertSettingsMessage settingsMessage;
public SimplePriceAlertDemo(Layer1ApiProvider provider) {
this.provider = provider;
ListenableHelper.addListeners(provider, this);
declarationMessage = Layer1ApiSoundAlertDeclarationMessage.builder()
.setTriggerDescription("Trade price > 10")
.setSource(SimplePriceAlertDemo.class)
.setPopupAllowed(true)
.setAliasMatcher(Layer1ApiSoundAlertDeclarationMessage.ALIAS_MATCH_ALL)
.build();
provider.sendUserMessage(declarationMessage);
settingsMessage = Layer1ApiAlertSettingsMessage
.builder()
.setDeclarationId(declarationMessage.id)
.setPopup(true)
.setSource(SimplePriceAlertDemo.class)
.build();
provider.sendUserMessage(settingsMessage);
}
@Override
public void onUserMessage(Object data) {
if (data instanceof Layer1ApiSoundAlertDeclarationMessage) {
Layer1ApiSoundAlertDeclarationMessage obtainedDeclarationMessage = (Layer1ApiSoundAlertDeclarationMessage) data;
if (obtainedDeclarationMessage.source == SimplePriceAlertDemo.class && !obtainedDeclarationMessage.isAdd) {
declarationMessage = null;
}
} else if (data instanceof Layer1ApiAlertSettingsMessage) {
Layer1ApiAlertSettingsMessage obtainedSettingsMessage = (Layer1ApiAlertSettingsMessage) data;
if (obtainedSettingsMessage.source == SimplePriceAlertDemo.class) {
settingsMessage = (Layer1ApiAlertSettingsMessage) data;
}
}
}
Key points here: we are sending the settings message from the constructor, linking it to a specific declaration by id. As we decided in the very beginning, our notification has a popup, but no sound alert - and it is reflected in settings message creation.
Moving on to the next step in the workflow:
-
When a trigger, defined internally by the addon occurs - send an actual alert
with
Layer1ApiSoundAlertMessage
Now that we have everything in place, we are ready to send the actual notification:
@Override
public void onTrade(String alias, double price, int size, TradeInfo tradeInfo) {
if (price > 10) {
System.out.printf("Trade of price > 10 occurred, actual price={%.2f}, size={%d}%n", price, size);
Layer1ApiSoundAlertMessage soundAlertMessage = Layer1ApiSoundAlertMessage.builder()
.setAlias(alias)
.setTextInfo(String.format("Trade actual price={%.2f}, size={%d}%n", price, size))
.setAdditionalInfo("Trade of price > 10")
.setShowPopup(settingsMessage.popup)
.setAlertDeclarationId(declarationMessage.id)
.setSource(SimplePriceAlertDemo.class)
.build();
provider.sendUserMessage(soundAlertMessage);
}
}
Here we specify all the data we want to show on the alert. Note that this message
is linked with the Layer1ApiSoundAlertDeclarationMessage by the
alertDeclarationId field, and the settings
(popup/sound notifications status) are taken from the stored Layer1ApiAlertSettingsMessage.
This ensures data integrity between Bookmap and your addon.
Note that although Layer1ApiSoundAlertMessage has many fields which might
look intimidating, only the source and the declaration id are mandatory.
If you did everything correctly, the addon should be able to send notifications now, and you can also control the popup state (on/off) with the Bookmap GUI (by pressing the "toggle popup" button in "Configure alerts" table)
Full example source code can be found at DemoStrategies project on Github - check outvelox.api.layer1.simpledemo.alerts.tradeprice.SimplePriceAlertDemo- See Also:
- DemoStrategies
-
Nested Class Summary
Nested Classes Modifier and Type Class Description static classLayer1ApiSoundAlertMessage.BuilderBuilder to buildLayer1ApiSoundAlertMessage.static classLayer1ApiSoundAlertMessage.SoundAlertStatusstatic interfaceLayer1ApiSoundAlertMessage.SoundAlertStatusListener -
Field Summary
Fields Modifier and Type Field Description java.lang.StringadditionalInfoIf not null, this text will be shown on the alert popup below the main message, specified intextInfo.java.lang.StringalertDeclarationIdTheLayer1ApiSoundAlertDeclarationMessage.idof a linked alert declaration
Note that if you specify this field, there should already exist a registered declaration with the given id.java.lang.StringalertIdThis ID can be used to reference alert later (stop it)java.lang.StringaliasIf not null the alert is considered linked to specific instrumentjava.lang.ObjectmetadataThis field will not be used anywhere outside of strategy code, but strategy can later use this field, for example, to determine if this message should be cancelled inLayer1ApiSoundAlertCancelMessage.Layer1ApiSoundMessagesFilter.shouldCancelMessage(Layer1ApiSoundAlertMessage)intpriorityThe priority value helps to order sound messages in a sound play queue.static longREPEAT_COUNT_INFINITElongrepeatCountNumber of times sound will be repeated orREPEAT_COUNT_INFINITEfor infinite replay (until cancelled by user).java.time.DurationrepeatDelayDelay between sound repetitions.java.awt.ImageseverityIconIf not null, the icon is shown on the alert popup.booleanshowPopupIf true, popup will be shown containingtextInfo(which must not be null)byte[]soundBinary data for the sound to be played.java.lang.Class<?>sourceClass that created this messageLayer1ApiSoundAlertMessage.SoundAlertStatusListenerstatusListenerjava.lang.StringtextInfoText description of a message, will be displyed in alerts dialog -
Constructor Summary
Constructors Constructor Description Layer1ApiSoundAlertMessage(byte[] sound, java.lang.String textInfo, long repeatCount, java.time.Duration repeatDelay, Layer1ApiSoundAlertMessage.SoundAlertStatusListener statusListener, java.lang.Class<?> source, java.lang.Object metadata)Deprecated.Layer1ApiSoundAlertMessage(java.lang.String alertId)Deprecated. -
Method Summary
Modifier and Type Method Description static Layer1ApiSoundAlertMessage.Builderbuilder()Creates builder to buildLayer1ApiSoundAlertMessage.booleanisCancelMessage()True if the message was created viaLayer1ApiSoundAlertMessage(java.lang.String)constructorjava.lang.StringtoString()
-
Field Details
-
REPEAT_COUNT_INFINITE
public static final long REPEAT_COUNT_INFINITE- See Also:
- Constant Field Values
-
sound
public final byte[] soundBinary data for the sound to be played. Please do not modify array after you pass it to the message. Multiple messages can share same sound data. -
textInfo
public final java.lang.String textInfoText description of a message, will be displyed in alerts dialog -
showPopup
public final boolean showPopupIf true, popup will be shown containingtextInfo(which must not be null) -
repeatCount
public final long repeatCountNumber of times sound will be repeated orREPEAT_COUNT_INFINITEfor infinite replay (until cancelled by user). -
repeatDelay
public final java.time.Duration repeatDelayDelay between sound repetitions. -
alertId
public final java.lang.String alertIdThis ID can be used to reference alert later (stop it) -
statusListener
-
source
public final java.lang.Class<?> sourceClass that created this message -
metadata
public final java.lang.Object metadataThis field will not be used anywhere outside of strategy code, but strategy can later use this field, for example, to determine if this message should be cancelled inLayer1ApiSoundAlertCancelMessage.Layer1ApiSoundMessagesFilter.shouldCancelMessage(Layer1ApiSoundAlertMessage) -
alias
public final java.lang.String aliasIf not null the alert is considered linked to specific instrument -
alertDeclarationId
public final java.lang.String alertDeclarationIdThe
Layer1ApiSoundAlertDeclarationMessage.idof a linked alert declaration
Note that if you specify this field, there should already exist a registered declaration with the given id.Also, the fields of the alert and its linked declaration are checked for conformity, e. g. for a message with
repeatCount= 1 you cannot link a declaration message withLayer1ApiSoundAlertDeclarationMessage.isRepeated= true -
priority
public final int priorityThe priority value helps to order sound messages in a sound play queue. If there are 2 or more messages waiting in a queue, the message with higher priority will be handled first. Order of handling messages with equal priority is not guaranteed. Default is 0 -
additionalInfo
public final java.lang.String additionalInfoIf not null, this text will be shown on the alert popup below the main message, specified intextInfo. This field is optional -
severityIcon
public final java.awt.Image severityIconIf not null, the icon is shown on the alert popup. You can add your own image or use the defaults from theLayer1DefaultAlertIcons
Default value is null
-
-
Constructor Details
-
Layer1ApiSoundAlertMessage
@Deprecated public Layer1ApiSoundAlertMessage(byte[] sound, java.lang.String textInfo, long repeatCount, java.time.Duration repeatDelay, Layer1ApiSoundAlertMessage.SoundAlertStatusListener statusListener, java.lang.Class<?> source, java.lang.Object metadata)Deprecated.Creates a message that will launch an alert. UseLayer1ApiSoundAlertMessage.Builderinstead. -
Layer1ApiSoundAlertMessage
@Deprecated public Layer1ApiSoundAlertMessage(java.lang.String alertId)Deprecated.Creates a message that will stop an alert with specified alertId
-
-
Method Details
-
isCancelMessage
public boolean isCancelMessage()True if the message was created viaLayer1ApiSoundAlertMessage(java.lang.String)constructor- Returns:
- flag if the message is actually a cancel message
-
toString
public java.lang.String toString()- Overrides:
toStringin classjava.lang.Object
-
builder
Creates builder to buildLayer1ApiSoundAlertMessage.- Returns:
- created builder
-