Coding Java strategies
Emit trades and information signals, configure orders, and attach chart metadata.
A QTSurfer strategy consumes market data, updates indicators and state, and emits signals. This guide focuses on signal emission: the point where an observation becomes either an instruction to trade or data to inspect later.
For the complete class API, use the Engine Javadoc, particularly the strategy
signal package. For agent-assisted authoring, install the maintained qtsurfer-java-strategy skill:
npx skills add QTSurfer/strategy-skills --skill qtsurfer-java-strategy The skill also covers choosing a strategy base class, configuring indicators, managing per-instrument state, and the Java subset accepted by the strategy compiler. Once the source is ready, compile and validate it through the API.
Execution signals and information signals
These signal families have different effects:
| Signal | Purpose | Causes a trade? |
|---|---|---|
BuySignal | Expresses a buy instruction and its order configuration | Yes |
SellSignal | Expresses a sell instruction and its order configuration | Yes |
InfoStrategySignal | Records indicators, diagnostics, or visualization metadata | No |
An information signal labelled BUY is still only information. Conversely, emitBuy(price) emits an executable buy signal even if no chart metadata is attached.
The README example deliberately emits both. It publishes an information signal on every window update so the indicator series can be inspected, but emits a buy or sell only when the moving averages cross:
InfoStrategySignal signal = createInfoSignal();
signal.set("fast", fast);
signal.set("slow", slow);
if (isBullish && !wasBullish) {
emitBuy(price);
} else if (!isBullish && wasBullish) {
emitSell(price);
}
emitSignal(signal); Signal helpers
Inside an AbstractWindowListener, the listener already knows its strategy and instrument:
| Helper | Result |
|---|---|
emitBuy(price) | Creates and immediately emits a market BuySignal |
emitSell(price) | Creates and immediately emits a market SellSignal |
createBuySignal(price) | Creates a buy signal to customize before emission |
createSellSignal(price) | Creates a sell signal to customize before emission |
createInfoSignal() | Creates an information signal to populate before emission |
emitInfo(key, values...) | Creates, populates, and immediately emits one information signal |
emitSignal(signal) | Emits a signal created or customized by the listener |
At strategy-class level the equivalent trade helpers take the instrument explicitly: emitBuy(instrument, price), emitSell(instrument, price), createBuySignal(instrument, price),
and createSellSignal(instrument, price). Use createInfoStrategySignal(instrument) when building
an information signal there.
The price passed to the basic trade helpers is the strategy’s current reference price. The
signal defaults to market; when a signal is changed to limit, that price becomes its limit
price.
Customizing buy and sell signals
The immediate helpers intentionally accept only a price. To configure an order, create its signal, set the required options, and emit it exactly once:
import com.wualabs.qtsurfer.engine.exchange.trade.OrderFlag;
import com.wualabs.qtsurfer.engine.strategy.event.signal.BuySignal;
import com.wualabs.qtsurfer.engine.strategy.event.signal.MarketHintSignal.OrderKind;
BuySignal buy = createBuySignal(price);
buy.setOrderKind(OrderKind.limit);
buy.setMaxTries(3);
buy.setFlags(OrderFlag.GTC);
buy.set("reason", "ema-cross");
emitSignal(buy); BuySignal and SellSignal inherit these options from MarketHintSignal, the common base class and authoritative method reference:
| Method | Meaning |
|---|---|
setOrderKind(OrderKind.market) | Market order; this is the default |
setOrderKind(OrderKind.limit) | Limit order at the signal’s price |
setMaxTries(n) | Maximum attempts for a limit buy; n must be positive |
setFlags(flags...) | Order flags such as FOK, IOC, or GTC; actual support depends on the venue |
setSellPercent(percent) | Percentage of the position to close; intended for multiple-long execution, default 100 |
setStopPrice(price) | Fixed protective stop to arm after the entry fills |
setStopLimitPrice(price) | Optional limit price for that fixed stop; without it the stop exits at market |
setTrailPercent(percent) | Trailing protective stop, expressed as a percentage from the running favourable price extreme |
setStopCondition(condition) | Live predicate that gates an engine-managed fixed or trailing stop |
set(key, values...) | Arbitrary analytics, provenance, or visualization metadata carried with the signal |
Treat stop and stopTrailing as engine-managed order kinds. Strategy code should express
protective risk on the entry signal with setStopPrice or setTrailPercent, rather than emitting a
standalone stop order.
Protective stops
A long entry can arm a fixed stop as part of the same signal:
BuySignal buy = createBuySignal(price);
buy.setStopPrice(price * 0.95);
emitSignal(buy); Use setStopLimitPrice as well when the protective exit must be stop-limit rather than
stop-market. A trailing stop follows the favourable extreme and triggers after the configured
percentage retracement:
BuySignal buy = createBuySignal(price);
buy.setTrailPercent(2.0);
emitSignal(buy); The same fields apply symmetrically to a short entry. A stop condition is evaluated repeatedly by
the engine and can suppress the stop until a wider strategy condition permits it. It is live
strategy logic, not serializable signal data. Because compiled strategies use a restricted Java
subset, use an anonymous BooleanSupplier rather than a lambda when one is needed.
Information and chart metadata
createInfoSignal() is listener-local syntactic sugar: it creates an InfoStrategySignal already
bound to the current strategy and instrument. Populate it with set and emit it when ready:
InfoStrategySignal signal = createInfoSignal();
signal.set("price", price);
signal.set("fast", fast);
signal.set("slow", slow);
emitSignal(signal); set stores one value directly. An even list of name/value pairs creates a nested object under the
given key, which is why chart markers use this form:
signal.set("_m",
"position", "belowBar",
"shape", "arrowUp",
"color", "#26a69a",
"text", "BUY"); The marker positions used by the standard visualization are aboveBar, belowBar, and inBar;
the portable shapes are circle, arrowUp, arrowDown, and square. Prefixing a property with _ reserves it as control metadata rather than a normal plotted series, as _m does here.
For a single value, emitInfo is the shortest form:
emitInfo("zscore", zscore); It also accepts nested name/value pairs:
emitInfo("averages", "fast", fast, "slow", slow); Use the longer createInfoSignal() form when one event needs several top-level values or marker
metadata. Information signals are useful for explaining a decision, but they never replace the
corresponding emitBuy or emitSell when the strategy is meant to trade.