Parsing StrategyQuant XML to Java: What 40+ Strategy Conversions Taught Us About Code Generation
Building a parser to convert StrategyQuant XML strategies into pure Java revealed hard truths about automated code generation for trading systems.
StrategyQuant generates trading strategies as XML files. These strategies are human-readable, structured documents that encode complete trading logic using indicators, operators, and action blocks. Our goal was straightforward: parse this XML and emit pure Java code that produces identical backtest results.
40 strategies later, the goal still sounds simple. The reality was not.
Three Export Paths, One Target
StrategyQuant offers three ways to get strategies out of the platform. Each serves a different purpose, and only one works for automated conversion.
Databank save (.sqx) produces proprietary binary or XML bundles that only StrategyQuant itself can open. Useless for a code generation pipeline.
Platform code export generates JForex, MT4, or MT5 source code directly. This is the path most users take when they want to run a strategy on a broker platform. The output is complete, compilable code tied to a specific platform API (JForex IStrategy, MQL Expert Advisor, etc.).
Strategy XML export produces a StrategyFile XML document containing the strategy's full structure: indicators, rules, parameters, money management, session filters. No platform-specific code. No compilation target. Just the logical skeleton of the strategy.
We chose the third path for our parser. The XML is the ground truth. Platform code is a rendering of it. By parsing the XML directly, we decouple conversion from any specific target language.
The XML Structure
A StrategyQuant strategy XML document has a consistent topology:
StrategyFile[@Version]
- Strategy[@name, @engine]
- Note, Description
- MoneyManagement[@type]
- GlobalSLPT
- Rules
- Events
- Event[@key=OnInit]
- Event[@key=OnBarUpdate]
- Rule[@type=Signal]
- signals/signal[@variable=UUID]
- Item[@key, @categoryType, @returnType]
- Param[@key, @type, @variable?]
- Block -> nested Item ...
- Variables
- variable [id, name, type, value, paramType]
The critical insight: logic is a tree of Item elements, not flat conditions.
- Operators like
AND,Not,IsFalling,IsLowerCountcombine or transform signals - Indicators like
LowestInRange,LinearRegression,Vortex,KeltnerChannel,ATRproduce numeric or boolean outputs - Actions like
EnterAtStop,CloseAllPositionsexecute trades - Controls like
MarketPositionIsLong,BooleanVariablemanage state
Each Item carries a key (stable identifier for Freemarker templates), a display template with #Param# placeholders, and nested Param or Block children.
The Three Hard Problems
1. Parameter Resolution
Parameters in the XML come in three flavors: literal values, external variables (exposed in the StrategyQuant UI), and auto-generated internal parameters. The variable attribute on a Param element links it to a Variable definition elsewhere in the document. Resolving these links requires a two-pass approach: first collect all Variables, then walk the Item tree resolving references.
A StopLoss parameter can be a literal integer (50 pips), a variable named MyStopLoss, or a computed value from another indicator. The parser must handle all three without losing the semantic meaning.
2. Indicator Mapping
StrategyQuant ships with 150+ built-in indicators. Each has a key string, a set of parameters, and a return type. Our Indicators class in trading-core implements about 40 of them directly. For the rest, we had to either implement new indicators or decompose complex SQ indicators into combinations of simpler ones.
Vortex indicator is a good example. In StrategyQuant it is a single building block. In pure Java, it is a multi-step calculation involving true range, upward and downward price movements, and smoothing periods. The parser's job is not just to call a function but to emit the correct sequence of intermediate calculations.
3. Order State Management
JForex strategies interact with an IEngine object that manages order lifecycle automatically. In our pure Java Strategy interface, each strategy manages its own order queue via getPendingOrders(). The parser must translate implicit engine interactions (submit, modify, cancel) into explicit queue operations.
The most subtle bug: after a partial fill, StrategyQuant's internal state model differs from our queue model. An EnterAtStop order that gets partially filled leaves the engine in a state where the remaining quantity is still pending. Our first parser version dropped the remainder. It took comparing backtest output bar by bar to catch the discrepancy.
What We Built
The parser lives in the trading-parser module. It produces Strategy implementations that compile against trading-core and run in the BacktestEngine. Each generated strategy goes through a golden backtest comparison: run the strategy against historical data and compare every order, every P&L, every drawdown against a hand-verified baseline.
The current pipeline processes 40+ strategies across three categories:
- Prop strategies: hand-written and hand-ported, no generation needed
- SQ-imported strategies: parsed from XML, Java generated, golden tested
- Creative strategies: designed in code directly, combining elements from both sources
Golden Baseline Verification
Every converted strategy earns its golden baseline. The process:
- Run the strategy against 10+ years of EUR/USD hourly data
- Record every bar event, every order submission, every fill
- Compare against a reference run (either StrategyQuant's own output or a manually verified Java run)
- Flag discrepancies at the bar level
The backtest engine records RunEvent objects: bar processed, order submitted, order filled, order cancelled, P&L snapshot. Two runs are identical if their event streams are identical. Not just the final equity curve. Every intermediate state.
This caught the partial-fill bug. It caught an endianness issue in the binary BarStore format (bytes stored in the wrong order for certain data columns). It caught a slippage parameter order swap in strategy constructors. Without event-level comparison, these would have gone unnoticed until live trading.
What the Pipeline Looks Like Today
Each new SQ strategy follows the same path:
- Export as XML from StrategyQuant
- Run
SqXmlParserto produce aJavaGenStrategy - Run golden backtest against the baseline
- If green, commit the generated strategy and its baseline
- Register in
StrategyCatalogfor paper trading
The Close loop label in our commit history marks strategies that have completed this pipeline: golden backtest passed, artifacts committed, deployed through Docker Compose for paper or live execution. As of today, the close loop has graduated strategies from V1 through V5, with the latest batch including HMM-based regime detection and post-news absorption models.
Lessons for Code Generation at Scale
XML is better than source-to-source. Parsing StrategyQuant's XML and generating fresh Java code gives cleaner output than trying to regex-transform JForex source. The XML has explicit structure. JForex code has implicit engine state. Always choose the more structured input format.
Event streams beat equity curves. A final P&L number can match while intermediate logic diverges. Record every event and compare at the bar level. Anything less is a false green.
Parameter resolution needs two passes. Variables can reference other variables. Indicators can depend on parameters that are themselves computed by other indicators. A single-pass parser misses these transitive dependencies.
Build an exhaustive indicator library first. The parser is only as good as the indicators it can generate. We delayed the parser by three weeks while implementing indicators that StrategyQuant used but we had not yet ported. Map the full indicator surface before writing the code generator.
40 strategies is a validation threshold. After 15-20 conversions, the pattern is clear. After 40, you have hit most edge cases. The remaining ones are rare enough that manual intervention is acceptable. The parser does not need to handle every strategy, just 95% of them. The last 5% get a hand-crafted Java implementation and a golden test.
The parser will never handle 100% of StrategyQuant output. That is the right design choice. Automated conversion handles the bulk of the work. Manual porting catches the outliers. The golden baseline verifies both.