Skip to main content

CustomSettingsPanelProvider - Enriched API Documentation

Executive Summary​

CustomSettingsPanelProvider is an interface in the Bookmap Simplified API that enables add-ons to create custom GUI panels for user configuration. It provides full control over the settings UI, as an alternative to the automatic UI generation provided by the @Parameter annotation. The interface is designed for scenarios requiring complex layouts, dynamic controls, real-time updates, or specialized input components.


Table of Contents​

  1. Interface Overview
  2. Method Reference
  3. Comparison with @Parameter Annotation
  4. Related Classes
  5. Implementation Patterns
  6. Common UI Components
  7. Settings Persistence
  8. Disabled State UI
  9. Complete Examples
  10. Edge Cases and Gotchas
  11. Best Practices
  12. Document Metadata

Interface Overview​

Package and Declaration​

package velox.api.layer1.simplified;

public interface CustomSettingsPanelProvider

Purpose: Allows specifying custom UI directly for module configuration.

Official Description​

Allows specifying custom UI directly. Alternatively you can use Parameter for automatic UI generation.

This UI is only shown when module is enabled. If you want to show some placeholder (usually a disabled version of UI with default values) when module is disabled - implement the following method:

public static StrategyPanel[] getCustomDisabledSettingsPanels()

This functionality is, strictly speaking, independent of this interface, but you will typically want to use both together. Important: If you use an obfuscator make sure to exclude this method.

Key Characteristics​

  • Single-method interface (functional interface candidate)
  • Returns array of StrategyPanel objects
  • Panels displayed in Bookmap's strategy dialog
  • Only shown when module is enabled
  • Complementary static method for disabled state UI

When to Use​

ScenarioUse CustomSettingsPanelProviderUse @Parameter
Simple numeric/boolean/color settingsҝŒ OverkillÒœ… Recommended
Complex layouts with multiple sectionsÒœ… RequiredҝŒ Not possible
Dynamic UI (add/remove rows)Òœ… RequiredҝŒ Not possible
Real-time statistics displayÒœ… RequiredҝŒ Not possible
Custom input components (sliders, charts)Òœ… RequiredҝŒ Not possible
Color pickers with previewÒœ… RecommendedÒő ï¸ Basic only
Interdependent settingsÒœ… RecommendedҝŒ Difficult

Method Reference​

getCustomSettingsPanels()​

velox.gui.StrategyPanel[] getCustomSettingsPanels()

Returns: Array of StrategyPanel objects to display in the strategy configuration dialog.

Description: Called by Bookmap when the user opens the configuration dialog for an enabled module. Each panel appears as a collapsible section with a title.

Return Value Details:

  • First panel in array appears topmost in the dialog
  • Panels are displayed in array order
  • Empty array new StrategyPanel[0] is valid (no custom UI)
  • null return is not recommended (may cause issues)

When Called:

  • When user clicks the settings button for the module
  • When StrategyPanel.requestReload() is called
  • After api.reload() completes (new instance created)

Threading:

  • Called on the Swing Event Dispatch Thread (EDT)
  • UI components must be created/modified on EDT
  • Long-running operations should use background threads

Confidence: HIGH (directly documented, verified in examples)


Comparison with @Parameter Annotation​

@Parameter Annotation (Automatic UI)​

@Parameter(name = "Line Width", minimum = 1, maximum = 10, step = 1)
public int lineWidth = 3;

@Parameter(name = "Show Indicator")
public boolean showIndicator = true;

@Parameter(name = "Indicator Color")
public Color indicatorColor = Color.GREEN;

Supported Types:

  • Byte, Short, Integer, Long, Float, Double Ò†’ Spinner
  • String Ò†’ Text field
  • Boolean Ò†’ Checkbox
  • Color Ò†’ Color picker

Limitations:

  • Fixed layout (label + control per row)
  • No grouping or sections
  • No dynamic behavior
  • No custom validation
  • No real-time preview

CustomSettingsPanelProvider (Manual UI)​

@Override
public StrategyPanel[] getCustomSettingsPanels() {
StrategyPanel p1 = new StrategyPanel("Line Settings");

JSpinner widthSpinner = new JSpinner(new SpinnerNumberModel(lineWidth, 1, 10, 1));
widthSpinner.addChangeListener(e -> {
lineWidth = (Integer) widthSpinner.getValue();
indicator.setWidth(lineWidth); // Real-time update!
});

p1.setLayout(new BorderLayout());
p1.add(new JLabel("Line Width:"), BorderLayout.WEST);
p1.add(widthSpinner, BorderLayout.CENTER);

return new StrategyPanel[] { p1 };
}

Advantages:

  • Full layout control (BorderLayout, GridBagLayout, etc.)
  • Multiple panels with logical grouping
  • Dynamic UI elements
  • Real-time indicator updates
  • Custom validation and feedback
  • Complex interdependencies

StrategyPanel​

velox.gui.StrategyPanel extends JPanel

Purpose: Container for settings UI with automatic styling.

Key Features:

  • Automatic border and title styling
  • Built-in enabled/disabled state management
  • Reload request capability
  • Localization support

Constructors:

StrategyPanel(String title)
StrategyPanel(String title, LayoutManager layout)
StrategyPanel(String title, boolean isDoubleBuffered)
StrategyPanel(String title, LayoutManager layout, boolean isDoubleBuffered)

Key Methods:

void setEnabled(boolean enabled)      // Enable/disable all child components
void requestReload() // Trigger panel refresh
void addReloadListener(Runnable r) // Listen for reload requests
String getTitle() // Get panel title
void setLocalizedTitle(TranslatableComponent title) // Set localized title

Styling Behavior:

  • title != null Ò†’ Displays with styled border and title
  • title == null Ò†’ No border, no title (plain panel)

SimpleStrategyPanel​

velox.api.layer1.simplified.SimpleStrategyPanel extends StrategyPanel

Purpose: Simplified panel with grid-based layout helpers.

Key Methods:

void addItem(String label, Component c)           // Label + component row
void addItems(String label, Component... c) // Label + multiple components
void addItems(Component... components) // Components only
void addItems(Component[] c, int[] gridWidths) // Custom grid widths

Example:

SimpleStrategyPanel panel = new SimpleStrategyPanel("Settings");
panel.addItem("Line Width:", new JSpinner(new SpinnerNumberModel(3, 1, 10, 1)));
panel.addItem("Color:", new ColorsConfigItem(Color.RED, Color.RED, c -> {}));

ColorsConfigItem​

velox.gui.colors.ColorsConfigItem extends JPanel

Purpose: Bookmap-styled color picker component.

Key Constructors:

// Simple version (most common)
ColorsConfigItem(Color currentColor, Color defaultColor, Consumer<Color> colorChangedListener)

// With label
ColorsConfigItem(Color currentColor, Color defaultColor, String label, Consumer<Color> colorChangedListener)

Features:

  • Matches Bookmap's native color picker styling
  • Reset-to-default button
  • Color preview
  • Callback on color change (including reset)

Example:

ColorsConfigItem colorPicker = new ColorsConfigItem(
settings.lineColor, // Current color
Color.GREEN, // Default color
"Line Color", // Label (optional)
color -> {
settings.lineColor = color;
indicator.setColor(color);
api.setSettings(settings);
}
);

Important Note:

ColorsConfigItem currently can trigger callback during construction

This means you should guard against null api references in the callback:

ColorsConfigItem colorPicker = new ColorsConfigItem(
settings.lineColor, Color.GREEN, color -> {
settings.lineColor = color;
if (api != null) { // Guard against construction-time callback
api.setSettings(settings);
indicator.setColor(color);
}
}
);

GuiUtils​

velox.gui.utils.GuiUtils

Purpose: Utility methods for GUI manipulation.

Key Method:

static void setPanelEnabled(JPanel panel, Boolean isEnabled)

Recursively enables/disables all components within a panel. Essential for creating disabled state UI.

Example:

public static StrategyPanel[] getCustomDisabledSettingsPanels() {
StrategyPanel[] panels = createPanels(new Settings(), null, null);
for (StrategyPanel panel : panels) {
GuiUtils.setPanelEnabled(panel, false);
}
return panels;
}

Implementation Patterns​

Pattern 1: Basic Single Panel​

@Layer1SimpleAttachable
@Layer1StrategyName("Basic Settings Demo")
@Layer1ApiVersion(Layer1ApiVersionValue.VERSION2)
public class BasicSettingsDemo implements CustomModule, CustomSettingsPanelProvider {

private Api api;
private int threshold = 10;

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

@Override
public StrategyPanel[] getCustomSettingsPanels() {
StrategyPanel panel = new StrategyPanel("Settings");
panel.setLayout(new BorderLayout());

JSpinner spinner = new JSpinner(new SpinnerNumberModel(threshold, 1, 100, 1));
spinner.addChangeListener(e -> {
threshold = (Integer) spinner.getValue();
});

panel.add(new JLabel("Threshold:"), BorderLayout.WEST);
panel.add(spinner, BorderLayout.CENTER);

return new StrategyPanel[] { panel };
}

@Override
public void stop() {}
}

Pattern 2: Multiple Panels with Settings Persistence​

@Layer1SimpleAttachable
@Layer1StrategyName("Multi-Panel Demo")
@Layer1ApiVersion(Layer1ApiVersionValue.VERSION2)
public class MultiPanelDemo implements CustomModule, CustomSettingsPanelProvider {

@StrategySettingsVersion(currentVersion = 1, compatibleVersions = {})
public static class Settings {
public int lineWidth = 3;
public Color lineColor = Color.GREEN;
public boolean showWidget = true;
}

private Api api;
private Settings settings;
private IndicatorModifiable indicator;

@Override
public void initialize(String alias, InstrumentInfo info, Api api, InitialState initialState) {
this.api = api;
this.settings = api.getSettings(Settings.class);

indicator = api.registerIndicatorModifiable("Demo", GraphType.BOTTOM);
indicator.setWidth(settings.lineWidth);
indicator.setColor(settings.lineColor);
}

@Override
public StrategyPanel[] getCustomSettingsPanels() {
// Panel 1: Visual Settings
StrategyPanel visualPanel = new StrategyPanel("Visual Settings");
visualPanel.setLayout(new BorderLayout(5, 5));

JSpinner widthSpinner = new JSpinner(new SpinnerNumberModel(settings.lineWidth, 1, 10, 1));
widthSpinner.addChangeListener(e -> {
settings.lineWidth = (Integer) widthSpinner.getValue();
api.setSettings(settings);
indicator.setWidth(settings.lineWidth);
});

visualPanel.add(new JLabel("Line Width:"), BorderLayout.WEST);
visualPanel.add(widthSpinner, BorderLayout.CENTER);

// Panel 2: Color Settings
StrategyPanel colorPanel = new StrategyPanel("Color Settings");
colorPanel.setLayout(new BorderLayout());

ColorsConfigItem colorPicker = new ColorsConfigItem(
settings.lineColor, Color.GREEN, color -> {
settings.lineColor = color;
if (api != null) {
api.setSettings(settings);
indicator.setColor(color);
}
}
);
colorPanel.add(colorPicker, BorderLayout.CENTER);

return new StrategyPanel[] { visualPanel, colorPanel };
}

@Override
public void stop() {}
}

Pattern 3: Dynamic UI with Add/Remove Rows​

Based on StopLossRandomizerSettings.java:

@Layer1SimpleAttachable
@Layer1StrategyName("Dynamic Rows Demo")
@Layer1ApiVersion(Layer1ApiVersionValue.VERSION1)
public class DynamicRowsDemo implements CustomModule, CustomSettingsPanelProvider {

@StrategySettingsVersion(currentVersion = 1, compatibleVersions = {})
public static class Settings {
public int numRows = 3;
}

private Api api;
private Settings settings;
private StrategyPanel panel;

@Override
public void initialize(String alias, InstrumentInfo info, Api api, InitialState initialState) {
this.api = api;
this.settings = api.getSettings(Settings.class);
}

@Override
public StrategyPanel[] getCustomSettingsPanels() {
updatePanel();
return new StrategyPanel[] { panel };
}

private void updatePanel() {
panel = new StrategyPanel("Dynamic Settings");
panel.setLayout(new GridBagLayout());
GridBagConstraints gbc = new GridBagConstraints();
gbc.fill = GridBagConstraints.HORIZONTAL;
gbc.insets = new Insets(2, 2, 2, 2);

// Header row
gbc.gridy = 0;
gbc.gridx = 0; panel.add(new JLabel("Column A"), gbc);
gbc.gridx = 1; panel.add(new JLabel("Column B"), gbc);
gbc.gridx = 2; panel.add(new JLabel("Column C"), gbc);

// Dynamic rows
for (int i = 0; i < settings.numRows; i++) {
gbc.gridy = i + 1;
gbc.gridx = 0; panel.add(new JComboBox<>(new Integer[]{1, 2, 3, 4, 5}), gbc);
gbc.gridx = 1; panel.add(new JComboBox<>(new Integer[]{1, 2, 3, 4, 5}), gbc);
gbc.gridx = 2; panel.add(new JComboBox<>(new Integer[]{1, 2, 3, 4, 5}), gbc);
}

// Add button
gbc.gridy = settings.numRows + 1;
gbc.gridx = 2;
JButton addButton = new JButton("Add Row");
addButton.addActionListener(e -> {
settings.numRows++;
updatePanel();
// Force UI refresh
panel.getParent().revalidate();
panel.getParent().repaint();
});
panel.add(addButton, gbc);
}

@Override
public void stop() {
api.setSettings(settings);
}
}

Pattern 4: Real-Time Statistics Display​

Based on AtrTrailingStopSettings.java:

@Override
public StrategyPanel[] getCustomSettingsPanels() {
// ... parameter panels ...

// Statistics panel with live-updating labels
StrategyPanel statsPanel = new StrategyPanel("Current Values");
statsPanel.setLayout(new GridLayout(2, 2, 5, 5));

statsPanel.add(new JLabel("Last True Range:"));
statsPanel.add(labelTr); // Updated by onAtrUpdated()

statsPanel.add(new JLabel("Average True Range:"));
statsPanel.add(labelAtr); // Updated by onAtrUpdated()

return new StrategyPanel[] { parametersPanel, statsPanel };
}

// Called from data processing logic
protected void onAtrUpdated(double tr, double atr) {
// Update labels on EDT
SwingUtilities.invokeLater(() -> {
labelTr.setText(String.format("%.3f", tr));
labelAtr.setText(String.format("%.3f", atr));
});
}

Pattern 5: Reload Button for Settings Changes​

Based on SettingsAndUiDemo.java:

@Override
public StrategyPanel[] getCustomSettingsPanels() {
StrategyPanel panel = new StrategyPanel("Offset Settings");
panel.setLayout(new BorderLayout());

JSpinner offsetSpinner = new JSpinner(new SpinnerNumberModel(
settings.lastTradeOffset, -100, 100, 1));
offsetSpinner.addChangeListener(e -> {
settings.lastTradeOffset = (Integer) offsetSpinner.getValue();
api.setSettings(settings);
// Note: This affects new values only, not computed historical data
});

// Reload button for full recalculation
JButton reloadButton = new JButton("Apply and Reload");
reloadButton.addActionListener(e -> api.reload());

panel.add(offsetSpinner, BorderLayout.CENTER);
panel.add(reloadButton, BorderLayout.EAST);

return new StrategyPanel[] { panel };
}

Common UI Components​

JSpinner (Numeric Input)​

JSpinner spinner = new JSpinner(new SpinnerNumberModel(
initialValue, // Current value
minimum, // Minimum allowed
maximum, // Maximum allowed
step // Step increment
));
spinner.addChangeListener(e -> {
int value = (Integer) spinner.getValue();
// Handle change
});

JComboBox (Dropdown Selection)​

// With enum values
JComboBox<LineStyle> styleCombo = new JComboBox<>(LineStyle.values());
styleCombo.setSelectedItem(settings.lineStyle);
styleCombo.addActionListener(e -> {
settings.lineStyle = (LineStyle) styleCombo.getSelectedItem();
});

// With custom objects
JComboBox<Integer> intCombo = new JComboBox<>(new Integer[]{1, 5, 10, 15, 30, 60});
intCombo.setSelectedItem(settings.interval);
intCombo.addActionListener(e -> {
settings.interval = (Integer) intCombo.getSelectedItem();
});

// Alignment fix (common pattern from examples)
((JLabel) comboBox.getRenderer()).setHorizontalAlignment(JLabel.LEFT);

JButton (Actions)​

JButton resetButton = new JButton("Restore Defaults");
resetButton.addActionListener(e -> {
settings = new Settings(); // Reset to defaults
api.setSettings(settings);
api.reload(); // Reload to apply
});

JLabel (Display Only)​

JLabel statusLabel = new JLabel("Ready");
// Update from processing thread:
SwingUtilities.invokeLater(() -> statusLabel.setText("Processing..."));

ColorsConfigItem (Color Picker)​

ColorsConfigItem colorPicker = new ColorsConfigItem(
currentColor,
defaultColor,
"Label Text", // Optional, can be null
color -> {
// Handle color change
if (api != null) {
settings.color = color;
api.setSettings(settings);
indicator.setColor(color);
}
}
);

Settings Persistence​

Settings Class Requirements​

  1. Annotate with @StrategySettingsVersion:
@StrategySettingsVersion(currentVersion = 1, compatibleVersions = {})
public static class Settings {
// Fields with default values
}
  1. All fields must be serializable (primitives, String, Color, enums, etc.)

  2. Provide default values for all fields

Loading Settings​

@Override
public void initialize(String alias, InstrumentInfo info, Api api, InitialState initialState) {
this.api = api;
// Load saved settings (or create new with defaults if none saved)
this.settings = api.getSettings(Settings.class);
}

Saving Settings​

// After any settings change
private void onSettingChanged() {
api.setSettings(settings); // Persist immediately
}

// Or in stop() for batch save
@Override
public void stop() {
api.setSettings(settings);
}

Version Migration​

@StrategySettingsVersion(currentVersion = 2, compatibleVersions = {1})
public static class Settings {
// v1 fields
public int oldField = 10;

// v2 new fields (must have defaults for migration)
public int newField = 20;
}

If stored settings version is not in compatibleVersions, default settings are used.


Disabled State UI​

Purpose​

When a module is not loaded (disabled), Bookmap can still display a placeholder UI showing default settings. This helps users understand what the module does before enabling it.

Implementation​

/**
* Optional static method for disabled state UI.
* IMPORTANT: If using an obfuscator, exclude this method!
*/
public static StrategyPanel[] getCustomDisabledSettingsPanels() {
// Create panels with default settings
Settings defaultSettings = new Settings();
StrategyPanel[] panels = createPanels(defaultSettings, null, null);

// Disable all components
for (StrategyPanel panel : panels) {
GuiUtils.setPanelEnabled(panel, false);
}

return panels;
}

// Reusable panel creation (used by both enabled and disabled states)
private static StrategyPanel[] createPanels(Settings settings, Api api, Indicator indicator) {
StrategyPanel panel = new StrategyPanel("Settings");

JSpinner spinner = new JSpinner(new SpinnerNumberModel(settings.lineWidth, 1, 10, 1));
spinner.addChangeListener(e -> {
if (api != null) { // Only respond when enabled
settings.lineWidth = (Integer) spinner.getValue();
api.setSettings(settings);
indicator.setWidth(settings.lineWidth);
}
});

panel.setLayout(new BorderLayout());
panel.add(spinner, BorderLayout.CENTER);

return new StrategyPanel[] { panel };
}

@Override
public StrategyPanel[] getCustomSettingsPanels() {
// Reuse same panel creation logic
return createPanels(settings, api, indicator);
}

Key Points​

  1. Static method - No instance required
  2. Standard name - Must be exactly getCustomDisabledSettingsPanels
  3. Returns StrategyPanel[] - Same return type as instance method
  4. Use GuiUtils.setPanelEnabled(panel, false) - Disables all child components
  5. Obfuscator exclusion - Method name must not be obfuscated

Complete Examples​

Example 1: Settings/UI Demo (From Official Examples)​

Based on SettingsAndUiDemo.java:

package velox.api.layer1.simplified.demo;

import java.awt.BorderLayout;
import java.awt.Color;
import javax.swing.*;

import velox.api.layer1.annotations.*;
import velox.api.layer1.data.InstrumentInfo;
import velox.api.layer1.data.TradeInfo;
import velox.api.layer1.messages.indicators.Layer1ApiUserMessageModifyIndicator.GraphType;
import velox.api.layer1.settings.StrategySettingsVersion;
import velox.api.layer1.simplified.*;
import velox.gui.StrategyPanel;
import velox.gui.colors.ColorsConfigItem;
import velox.gui.utils.GuiUtils;

@Layer1SimpleAttachable
@Layer1StrategyName("Settings/UI demo")
@Layer1ApiVersion(Layer1ApiVersionValue.VERSION2)
public class SettingsAndUiDemo implements
CustomModule,
TradeDataListener,
CustomSettingsPanelProvider
{
@StrategySettingsVersion(currentVersion = 1, compatibleVersions = {})
public static class Settings {
int lastTradeOffset = 0;
Color lastTradeColor = DEFAULT_LAST_TRADE_COLOR;
int lineWidth = 3;
LineStyle lineStyle = LineStyle.SOLID;
}

private static final Color DEFAULT_LAST_TRADE_COLOR = Color.GREEN;

private Api api;
private Indicator lastTradeIndicator;
private Settings settings;

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

lastTradeIndicator = api.registerIndicator("Last trade, no history", GraphType.PRIMARY);

settings = api.getSettings(Settings.class);
lastTradeIndicator.setColor(settings.lastTradeColor);
lastTradeIndicator.setLineStyle(settings.lineStyle);
lastTradeIndicator.setWidth(settings.lineWidth);
}

@Override
public void stop() {}

@Override
public void onTrade(double price, int size, TradeInfo tradeInfo) {
lastTradeIndicator.addPoint(price + settings.lastTradeOffset);
}

@Override
public StrategyPanel[] getCustomSettingsPanels() {
return getCustomSettingsPanels(settings, api, lastTradeIndicator);
}

/**
* Generate custom settings UI. Since we want to generate both disabled and
* enabled UI we move the code to separate method.
*/
private static StrategyPanel[] getCustomSettingsPanels(
Settings settings, Api api, Indicator lastTradeIndicator) {

// Panel 1: Offset with reload button
StrategyPanel p1 = new StrategyPanel("Last trade offset");
JSpinner offsetSpinner = new JSpinner(new SpinnerNumberModel(
settings.lastTradeOffset, -100, 100, 1));
offsetSpinner.addChangeListener(e -> {
settings.lastTradeOffset = (Integer) offsetSpinner.getValue();
api.setSettings(settings);
});

JButton reloadButton = new JButton("Apply and reload");
reloadButton.addActionListener(e -> api.reload());

p1.setLayout(new BorderLayout());
p1.add(offsetSpinner, BorderLayout.CENTER);
p1.add(reloadButton, BorderLayout.EAST);

// Panel 2: Color picker
StrategyPanel p2 = new StrategyPanel("Line color");
ColorsConfigItem colorConfig = new ColorsConfigItem(
settings.lastTradeColor, DEFAULT_LAST_TRADE_COLOR, "Last trade",
color -> {
settings.lastTradeColor = color;
if (api != null) {
api.setSettings(settings);
lastTradeIndicator.setColor(color);
}
});
p2.setLayout(new BorderLayout());
p2.add(colorConfig, BorderLayout.CENTER);

// Panel 3: Line width
StrategyPanel p3 = new StrategyPanel("Line width");
JSpinner lineWidthSpinner = new JSpinner(new SpinnerNumberModel(
settings.lineWidth, 1, 10, 1));
lineWidthSpinner.addChangeListener(e -> {
settings.lineWidth = (Integer) lineWidthSpinner.getValue();
api.setSettings(settings);
lastTradeIndicator.setWidth(settings.lineWidth);
});
p3.setLayout(new BorderLayout());
p3.add(lineWidthSpinner, BorderLayout.CENTER);

// Panel 4: Line style
StrategyPanel p4 = new StrategyPanel("Line style");
JComboBox<LineStyle> lineStyleComboBox = new JComboBox<>(LineStyle.values());
lineStyleComboBox.setSelectedItem(settings.lineStyle);
lineStyleComboBox.addActionListener(e -> {
settings.lineStyle = (LineStyle) lineStyleComboBox.getSelectedItem();
api.setSettings(settings);
lastTradeIndicator.setLineStyle(settings.lineStyle);
});
p4.setLayout(new BorderLayout());
p4.add(lineStyleComboBox, BorderLayout.CENTER);

StrategyPanel[] strategyPanels = new StrategyPanel[] {p1, p2, p3, p4};

// If module is not loaded - disable all components
if (api == null) {
for (StrategyPanel strategyPanel : strategyPanels) {
GuiUtils.setPanelEnabled(strategyPanel, false);
}
}

return strategyPanels;
}

/**
* Optional method generating UI for unloaded module.
*/
public static StrategyPanel[] getCustomDisabledSettingsPanels() {
return getCustomSettingsPanels(new Settings(), null, null);
}
}

Example 2: MBO Visualizer (Real-Time Display)​

Based on MboVisualizerNoHistory.java:

@Layer1SimpleAttachable
@Layer1StrategyName("Mbo visualizer: no history")
@Layer1ApiVersion(Layer1ApiVersionValue.VERSION2)
public class MboVisualizerNoHistory implements
CustomModule,
CustomSettingsPanelProvider,
MarketByOrderDepthDataListener {

private OrderBookMbo orderBookMbo = new OrderBookMbo();
private OrderBook orderBook = new OrderBook();
private JLabel displayLabel;
private AtomicBoolean updateIsScheduled = new AtomicBoolean();

public MboVisualizerNoHistory() {
// Initialize label on EDT
SwingUtilities.invokeLater(() -> {
displayLabel = new JLabel();
});
}

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

@Override
public void stop() {}

@Override
public void send(String orderId, boolean isBid, int price, int size) {
orderBookMbo.send(orderId, isBid, price, size);
synchronized (orderBook) {
long levelSize = orderBook.getSizeFor(isBid, price, 0) + size;
orderBook.onUpdate(isBid, price, levelSize);
}
scheduleUpdateIfNecessary();
}

@Override
public void replace(String orderId, int price, int size) {
// ... update logic ...
scheduleUpdateIfNecessary();
}

@Override
public void cancel(String orderId) {
// ... cancel logic ...
scheduleUpdateIfNecessary();
}

/**
* Throttled UI update - prevents flooding EDT with updates
*/
private void scheduleUpdateIfNecessary() {
boolean shouldSchedule = !updateIsScheduled.getAndSet(true);

if (shouldSchedule) {
SwingUtilities.invokeLater(() -> {
updateIsScheduled.set(false);

StringBuilder builder = new StringBuilder();
builder.append("<html>");

synchronized (orderBook) {
// Build display string from order book
// ...
}

builder.append("</html>");
displayLabel.setText(builder.toString());
});
}
}

@Override
public StrategyPanel[] getCustomSettingsPanels() {
// Reinitialize label for fresh panel
displayLabel = new JLabel();
scheduleUpdateIfNecessary();

StrategyPanel ordersPanel = new StrategyPanel("Order book");
ordersPanel.add(displayLabel);
return new StrategyPanel[] { ordersPanel };
}
}

Edge Cases and Gotchas​

1. ColorsConfigItem Callback During Construction​

Problem:

ColorsConfigItem colorPicker = new ColorsConfigItem(
settings.color, Color.GREEN, color -> {
settings.color = color;
api.setSettings(settings); // NPE if api is null!
indicator.setColor(color); // NPE if indicator is null!
}
);

Solution:

ColorsConfigItem colorPicker = new ColorsConfigItem(
settings.color, Color.GREEN, color -> {
settings.color = color;
if (api != null) { // Guard against construction-time callback
api.setSettings(settings);
indicator.setColor(color);
}
}
);

2. UI Updates from Non-EDT Thread​

Problem:

// Called from data processing thread
public void onTrade(double price, int size, TradeInfo tradeInfo) {
statusLabel.setText("Trade: " + size); // Thread violation!
}

Solution:

public void onTrade(double price, int size, TradeInfo tradeInfo) {
SwingUtilities.invokeLater(() -> {
statusLabel.setText("Trade: " + size);
});
}

3. Panel Refresh After Dynamic Changes​

Problem:

JButton addButton = new JButton("Add Row");
addButton.addActionListener(e -> {
settings.numRows++;
updatePanel(); // Panel rebuilt, but UI not refreshed
});

Solution:

addButton.addActionListener(e -> {
settings.numRows++;
updatePanel();
panel.getParent().revalidate(); // Force layout recalculation
panel.getParent().repaint(); // Force repaint
});

4. Settings Not Persisted​

Problem:

spinner.addChangeListener(e -> {
settings.value = (Integer) spinner.getValue();
// Forgot to call api.setSettings()!
});

Solution:

spinner.addChangeListener(e -> {
settings.value = (Integer) spinner.getValue();
api.setSettings(settings); // Persist immediately
});

5. Obfuscator Breaking Disabled UI​

Problem: Method getCustomDisabledSettingsPanels is renamed by obfuscator.

Solution: Configure obfuscator to exclude this method:

# ProGuard example
-keepclassmembers class * implements velox.api.layer1.simplified.CustomSettingsPanelProvider {
public static velox.gui.StrategyPanel[] getCustomDisabledSettingsPanels();
}

6. JComboBox Text Alignment​

Problem: JComboBox text appears centered, inconsistent with Bookmap style.

Solution:

JComboBox<String> combo = new JComboBox<>(options);
((JLabel) combo.getRenderer()).setHorizontalAlignment(JLabel.LEFT);

7. Null Return from getCustomSettingsPanels()​

Problem:

@Override
public StrategyPanel[] getCustomSettingsPanels() {
return null; // May cause issues!
}

Solution:

@Override
public StrategyPanel[] getCustomSettingsPanels() {
return new StrategyPanel[0]; // Empty array if no panels needed
}

Best Practices​

1. Separate Panel Creation Logic​

Extract panel creation to a static method usable by both enabled and disabled states:

private static StrategyPanel[] createPanels(Settings settings, Api api, Indicator indicator) {
// ... create panels ...
return panels;
}

@Override
public StrategyPanel[] getCustomSettingsPanels() {
return createPanels(settings, api, indicator);
}

public static StrategyPanel[] getCustomDisabledSettingsPanels() {
StrategyPanel[] panels = createPanels(new Settings(), null, null);
for (StrategyPanel p : panels) {
GuiUtils.setPanelEnabled(p, false);
}
return panels;
}

2. Real-Time Updates Without Reload​

For visual changes that don't require recalculation:

// Good: Immediate visual update
colorPicker.addActionListener(e -> {
settings.color = colorPicker.getColor();
api.setSettings(settings);
indicator.setColor(settings.color); // Immediate update
});

// Avoid: Unnecessary reload
colorPicker.addActionListener(e -> {
settings.color = colorPicker.getColor();
api.setSettings(settings);
api.reload(); // Overkill for color change
});

3. Provide Reload Button for Computed Values​

For settings that affect historical calculations:

JButton reloadButton = new JButton("Apply and Reload");
reloadButton.setToolTipText("Recalculates all historical data with new settings");
reloadButton.addActionListener(e -> api.reload());

4. Use SimpleStrategyPanel for Grid Layouts​

SimpleStrategyPanel panel = new SimpleStrategyPanel("Settings");
panel.addItem("Line Width:", widthSpinner);
panel.addItem("Color:", colorPicker);
panel.addItem("Style:", styleCombo);

5. Throttle Real-Time Display Updates​

private AtomicBoolean updateScheduled = new AtomicBoolean(false);

private void requestDisplayUpdate() {
if (!updateScheduled.getAndSet(true)) {
SwingUtilities.invokeLater(() -> {
updateScheduled.set(false);
refreshDisplay();
});
}
}
return new StrategyPanel[] {
createVisualSettingsPanel(), // Colors, widths, styles
createParameterPanel(), // Algorithm parameters
createStatisticsPanel() // Real-time statistics
};

Document Metadata​

Sources​

Primary Documentation:

  • CustomSettingsPanelProvider.md (Javadoc)
  • CustomSettingsPanelProvider__SimplifiedApiWrapper_7_7_0_API_.pdf (Official PDF)

Related Classes:

  • StrategyPanel.md - Panel container
  • SimpleStrategyPanel.md - Grid layout helper
  • ColorsConfigItem.md - Color picker component
  • GuiUtils.md - UI utility methods
  • Parameter.md - Alternative annotation-based approach
  • StrategySettingsVersion.md - Settings persistence annotation
  • Api.md - Settings API methods

Example Code Analyzed:

  • SettingsAndUiDemo.java - Complete reference implementation
  • AtrTrailingStopSettings.java - Complex multi-panel with statistics
  • VolumeTracker.java - Interval and type selection
  • MboVisualizerNoHistory.java - Real-time display panel
  • StopLossRandomizerSettings.java - Dynamic row addition

Confidence Assessment​

AspectConfidenceReason
OverallHIGHWell-documented with multiple examples
Method signatureHIGHDirectly documented in Javadoc
Return typeHIGHExplicitly StrategyPanel[]
Panel orderingHIGHFirst panel is topmost (confirmed in examples)
Disabled state methodHIGHExplicitly documented with obfuscator warning
Threading modelHIGHEDT requirement verified in examples
ColorsConfigItem callback timingHIGHExplicitly documented in SettingsAndUiDemo
Settings persistenceHIGHMultiple examples demonstrate pattern

Well-Documented Areas​

  1. Òœ… Interface purpose and method signature
  2. Òœ… Disabled state static method
  3. Òœ… Relationship with @Parameter annotation
  4. Òœ… StrategyPanel class usage
  5. Òœ… Settings persistence with @StrategySettingsVersion
  6. Òœ… ColorsConfigItem callback timing issue

Inferred/Enhanced Areas​

  1. 🔍 Threading requirements (inferred from Swing best practices)
  2. 🔍 Panel refresh patterns (derived from examples)
  3. 🔍 Throttling strategies (derived from MboVisualizerNoHistory)
  4. 🔍 Best practices (synthesized from multiple examples)

Gaps / Unknown Areas​

  1. Γ’Ββ€œ Maximum panel count - No documented limit
  2. Γ’Ββ€œ Panel size constraints - No explicit sizing documentation
  3. Γ’Ββ€œ Performance impact - No guidance on complex UI performance
  4. Γ’Ββ€œ Localization details - setLocalizedTitle usage unclear
  5. Γ’Ββ€œ requestReload() behavior - Exact refresh mechanism undocumented

Document Version: 1.0 Generated: 2025-01-24 API Interface: velox.api.layer1.simplified.CustomSettingsPanelProvider Confidence Level: HIGH Completeness: ~90% (missing only edge case documentation and performance guidance)