Phase 23 follow-up — DSL corrections and packaging¶
Summary¶
Two threads land together. The first closes four parser-vs-docs gaps discovered while building an end-to-end smoke test: snippets from the cookbook that the parser silently rejected. The second adds an installation path — a one-line shell installer that fetches the latest release tarball — plus a release workflow that produces the tarball on every v* tag, plus a CI-gated smoke test that exercises the full install → parse → backtest → Docker pipeline on every PR.
The DSL corrections are pure bug fixes: every change makes documented syntax work. The packaging additions are pure additions: nothing previously worked differently.
What's new¶
DSL¶
- Double-quoted strings.
LOG "hello"parses. Until now the lexer accepted only single quotes ('hello'), but every cookbook snippet uses double quotes. Both forms are interchangeable. Escapes (\",\\,\n,\t) work in either delimiter. =as equality in WHEN conditions.WHEN POSITION.btc = 0 THEN ...parses. The parser previously accepted only==; the docs uniformly used=. Both forms now mean equality. There is no grammatical ambiguity —=never had another meaning in expression position.- Multi-action rules separated by
;.THEN BUY btc SIZING 0.01 ; LOG "long"parses to aBlock(actions=[Buy, Log])that fires both actions on the same trigger. Previously everyTHENaccepted exactly one action. Newline-as-separator is not supported — only;. - Case-insensitive indicator names.
ema(btc.close, 9)works in addition toEMA(...). The DSL is otherwise case-insensitive for keywords; indicators were the outlier.
CLI¶
qkt --helplists every subcommand. The help text previously documented onlyparse,backtest,run— nine of the twelve subcommands were missing. The output now groups by purpose: strategy authoring, daemon lifecycle, daemon operations, venue/feed.
Packaging¶
scripts/install.sh— one-line curl-pipe installer: Fetches the latest GitHub release tarball, extracts to~/.local/share/qkt, symlinks~/.local/bin/qkt. HonorsQKT_VERSION,QKT_PREFIX,QKT_BIN_DIR,QKT_REPOenv vars. Verifies JDK 21 is present and prints PATH advice if needed..github/workflows/release.yml— runs./gradlew distTaron everyv*tag push, verifies the tarball containsbin/qkt, attaches to the existing release (or creates a draft if missing).tests/smoke-install.sh— end-to-end smoke. Builds the distribution, simulates a clean install in a temp directory, parses + backtests a tiny strategy, runs a real momentum strategy with--jsonvalidation, optionally builds the Docker image and runs the same backtest inside the container. Used both locally (bash tests/smoke-install.sh) and in CI.- CI smoke job.
.github/workflows/check.ymlruns the smoke script on every push tomainand every PR. Catches packaging regressions (Dockerfile drift, install layout) that unit tests can't see.
Migration from previous phase¶
The DSL fixes are pure additions — no existing strategy is invalidated.
| Before | After |
|---|---|
LOG 'long' (still works) |
LOG "long" (also works now) |
POSITION.btc == 0 (still works) |
POSITION.btc = 0 (also works now) |
THEN BUY btc (one action) |
THEN BUY btc ; LOG "long" (multi-action) |
EMA(btc.close, 9) (still works) |
ema(btc.close, 9) (also works now) |
Usage cookbook¶
Author a strategy that takes a trade and logs it¶
STRATEGY momentum VERSION 1
SYMBOLS
btc = BACKTEST:BTCUSD EVERY 1m
RULES
WHEN ema(btc.close, 5) CROSSES ABOVE ema(btc.close, 13)
AND POSITION.btc = 0
THEN BUY btc SIZING 0.01 ; LOG "long {p}" p=btc.close
WHEN ema(btc.close, 5) CROSSES BELOW ema(btc.close, 13)
AND POSITION.btc > 0
THEN CLOSE btc ; LOG "exit {p}" p=btc.close
Three of the four DSL changes show up: lowercase ema, = for equality, ; between actions.
Install qkt on a fresh box¶
curl -fsSL https://raw.githubusercontent.com/elitekaycy/qkt/main/scripts/install.sh | bash
# … or pin a version:
QKT_VERSION=v0.25.0 curl -fsSL https://raw.githubusercontent.com/elitekaycy/qkt/main/scripts/install.sh | bash
Then verify:
Run the smoke locally before pushing¶
bash tests/smoke-install.sh # full, incl. Docker
bash tests/smoke-install.sh --no-docker # skip the Docker stage
On failure the script preserves the temp directory and prints the log path. On success it cleans up.
Testing patterns¶
- Parse-level regressions are caught by
LexerTestandParserActionTest. New cases: double-quoted strings (tokenizes double-quoted strings,double-quoted strings support escapes); multi-actionBlock(multi-action rule with semicolon separator parses as Block). - Compiler-level regressions are caught by the full DSL test suite, which now runs the same
mergeDefaults/IterVarSubstitutionpaths forBlockactions as for single actions. - Packaging regressions are caught by
tests/smoke-install.shrunning in CI. The script intentionally exercises an installedqktbinary, not a Gradle invocation, so it would catch a broken distribution layout.
Known limitations¶
- Multi-action with newline separator is not supported. Only
;. Adding newline-as-separator would require a lexer change to emit aNEWLINEtoken; deferred. - The smoke test's
qkt runstep is gated behindQKT_SMOKE_RUN=1and skipped by default. Therunsubcommand needs a live tick feed (TradingView), which is unreliable in CI. An offline tick source would let this run unconditionally. scripts/install.shrequires JDK 21 to be already installed. Bundling a JDK would balloon the tarball; we expect operators to install Java themselves.
References¶
- DSL changes: lexer/parser/compiler under
src/main/kotlin/com/qkt/dsl/. - Installer:
scripts/install.sh. - Release workflow:
.github/workflows/release.yml. - Smoke test:
tests/smoke-install.sh. - CI smoke job:
.github/workflows/check.yml.