OrderBlockStrategy - Trading Strategy with Indicators
A complete trading strategy demonstrating custom settings panels, indicator registration, bar data processing, and order placement.
Features​
- Bar data analysis for order block detection
- Custom settings panel with persistent configuration
- Indicator with icon markers on chart
- Order placement with stop loss and take profit
- Bullish/Bearish order block identification
Key Concepts​
Settings with Persistence​
Using @StrategySettingsVersion for versioned, persistent settings:
@StrategySettingsVersion(currentVersion = 1, compatibleVersions = {})
public static class Settings {
int requiredSequentialCandles = 5;
double minPercentMove = 0.00;
}
// In initialize()
settings = api.getSettings(Settings.class);
Indicator Registration​
Register indicators on the primary chart:
indicator = api.registerIndicator("OrderBlockIndicator", GraphType.PRIMARY);
Adding Icons to Indicators​
Display visual markers at specific price levels:
indicator.addIcon(priceLevel, iconImage, iconCenterX, iconCenterY);
Bar Interval Configuration​
Implementing BarDataListener requires specifying the bar interval:
@Override
public long getInterval() {
return Intervals.INTERVAL_2_MINUTES;
}
Available intervals include:
Intervals.INTERVAL_1_MINUTEIntervals.INTERVAL_2_MINUTESIntervals.INTERVAL_5_MINUTESIntervals.INTERVAL_15_MINUTESIntervals.INTERVAL_1_HOUR
Custom Settings Panel​
Implement CustomSettingsPanelProvider for UI settings:
@Override
public StrategyPanel[] getCustomSettingsPanels() {
StrategyPanel p1 = new StrategyPanel("Required Sequential Candles");
JSpinner spinner = new JSpinner(new SpinnerNumberModel(
settings.requiredSequentialCandles, 1, 100, 1));
spinner.addChangeListener(e -> {
settings.requiredSequentialCandles = (Integer) spinner.getValue();
api.setSettings(settings); // Persist changes
});
p1.add(spinner);
return new StrategyPanel[] { p1 };
}
Complete Code​
package main;
import util.iconClass;
import velox.api.layer1.annotations.*;
import velox.api.layer1.common.Log;
import velox.api.layer1.data.InstrumentInfo;
import velox.api.layer1.data.OrderDuration;
import velox.api.layer1.data.SimpleOrderSendParameters;
import velox.api.layer1.data.SimpleOrderSendParametersBuilder;
import velox.api.layer1.layers.utils.OrderBook;
import velox.api.layer1.settings.StrategySettingsVersion;
import velox.api.layer1.simplified.*;
import velox.api.layer1.messages.indicators.Layer1ApiUserMessageModifyIndicator.GraphType;
import velox.gui.StrategyPanel;
import javax.swing.*;
import javax.swing.SpinnerNumberModel;
import java.awt.image.BufferedImage;
import java.util.ArrayList;
import java.util.List;
import java.util.Random;
@Layer1SimpleAttachable
@Layer1StrategyName("Order Blocks Strategy WW")
@Layer1ApiVersion(Layer1ApiVersionValue.VERSION1)
@Layer1TradingStrategy
public class OrderBlockStrategy2 implements BarDataListener, CustomModule,
TimeListener, CustomSettingsPanelProvider {
private static final String BEARISH_OB = "Bearish OB";
private static final String BULLISH_OB = "Bullish OB";
private Indicator indicator;
private int requiredSequentialCandles;
private double minPercentMove;
private final List<Bar> previousBars = new ArrayList<>();
private Api api;
private BufferedImage bearishIcon;
private BufferedImage bullishIcon;
private String alias;
@StrategySettingsVersion(currentVersion = 1, compatibleVersions = {})
public static class Settings {
int requiredSequentialCandles = 5;
double minPercentMove = 0.00;
}
private Settings settings;
@Override
public void initialize(String alias, InstrumentInfo info, Api api, InitialState initialState) {
this.api = api;
this.alias = alias;
settings = api.getSettings(Settings.class);
requiredSequentialCandles = settings.requiredSequentialCandles;
minPercentMove = settings.minPercentMove;
indicator = api.registerIndicator("OrderBlockIndicator", GraphType.PRIMARY);
iconClass bearishIconHandler = new iconClass(
"/icons8-arrow-down-30.png", "/default-arrow-down.png");
bearishIcon = bearishIconHandler.getIcon();
iconClass bullishIconHandler = new iconClass(
"/icons8-arrow-up-30.png", "/default-arrow-up.png");
bullishIcon = bullishIconHandler.getIcon();
}
private double calculatePercentChange(List<Bar> bars) {
if (bars == null || bars.size() < 2) {
Log.info("Not enough bars to calculate percent change. Returning 0.");
return 0.0;
}
double firstClose = bars.get(0).getClose();
double lastClose = bars.get(bars.size() - 1).getClose();
return ((lastClose - firstClose) / firstClose) * 100;
}
@Override
public void onTimestamp(long nanoseconds) {
// Handle timestamp logic if needed
}
@Override
public void onBar(OrderBook orderBook, Bar bar) {
int iconCenterX = (bearishIcon != null) ? bearishIcon.getWidth() / 2 : 0;
int iconCenterY = (bearishIcon != null) ? bearishIcon.getHeight() / 2 : 0;
if (previousBars.size() >= requiredSequentialCandles && indicator != null) {
double percentChange = calculatePercentChange(previousBars);
if (percentChange >= minPercentMove) {
// Detect bearish order block
if (previousBars.get(0).getClose() > previousBars.get(0).getOpen()
&& bar.getClose() < bar.getOpen()) {
Log.info(BEARISH_OB);
indicator.addIcon(previousBars.get(0).getHigh(),
bearishIcon, iconCenterX, iconCenterY);
}
// Detect bullish order block
else if (previousBars.get(0).getClose() < previousBars.get(0).getOpen()
&& bar.getClose() > bar.getOpen()) {
Log.info(BULLISH_OB);
indicator.addIcon(previousBars.get(0).getLow(),
bullishIcon, iconCenterX, iconCenterY);
}
}
}
previousBars.add(bar);
if (previousBars.size() > requiredSequentialCandles) {
previousBars.remove(0);
}
}
@Override
public long getInterval() {
return Intervals.INTERVAL_2_MINUTES;
}
@Override
public void stop() {
Log.info("OrderBlockAddOn stopped");
}
@Override
public StrategyPanel[] getCustomSettingsPanels() {
StrategyPanel p1 = new StrategyPanel("Required Sequential Candles");
JSpinner candlesSpinner = new JSpinner(
new SpinnerNumberModel(settings.requiredSequentialCandles, 1, 100, 1));
candlesSpinner.addChangeListener(e -> {
settings.requiredSequentialCandles = (Integer) candlesSpinner.getValue();
api.setSettings(settings);
});
p1.add(candlesSpinner);
StrategyPanel p2 = new StrategyPanel("Minimum Percentage Move");
JSpinner percentMoveSpinner = new JSpinner(
new SpinnerNumberModel(settings.minPercentMove, 0.0, 100.0, 0.1));
percentMoveSpinner.addChangeListener(e -> {
settings.minPercentMove = (Double) percentMoveSpinner.getValue();
api.setSettings(settings);
});
p2.add(percentMoveSpinner);
return new StrategyPanel[] { p1, p2 };
}
private void placeOrder() {
SimpleOrderSendParametersBuilder builder =
new SimpleOrderSendParametersBuilder(alias, true, 1);
builder.setStopLossOffset(10);
builder.setTakeProfitOffset(10);
builder.setDuration(OrderDuration.IOC);
SimpleOrderSendParameters order = builder.build();
api.sendOrder(order);
}
}
Order Block Detection Logic​
The strategy identifies order blocks based on candle patterns:
- Bearish Order Block: When a bullish candle (close > open) is followed by a bearish candle (close < open) after a sequence of candles with sufficient percent move
- Bullish Order Block: When a bearish candle (close < open) is followed by a bullish candle (close > open) after a sequence of candles with sufficient percent move
Key Takeaways​
- Use
@StrategySettingsVersionfor persistent, versioned settings - Implement
CustomSettingsPanelProviderfor user-configurable parameters - Register indicators with
api.registerIndicator()for chart overlays - Use
addIcon()to place visual markers at price levels - Implement
getInterval()to specify bar aggregation period - Use
@Layer1TradingStrategyannotation for order placement capability
See Also​
- Api Interface - Indicator registration and settings
- Order Placement - Order submission details
- Data Listeners - BarDataListener interface
- Getting Started - Basic add-on structure