Language & qSQL
Amber is a terse array notation with a q/kdb+ vocabulary bolted firmly on top. If you have written q you already know most of it; the rules below are the handful of places where the host grammar shows through, and they are properties of the host, not bugs.
Dialect notes (read these first)
- Dyadic library functions take brackets, not infix. Amber does not allow a
user-defined function to be applied infix —
x f yparses as two nouns. Writelj[t;kt],in[x;y],except[a;b],xasc[`sym;t]. Built-in verbs (+ - * % ! & | < > = ~ , ^ # _ $ ? @ .) are infix as usual. - No
>=or<=. Write~a<bfora>=band~a>bfora<=b. - Symbols cannot contain
_.`a_bis a parse error because_is a verb (drop, and floor). Use a quoted symbol`"a_b", a dotted name (my.name) or camel case. - Nested lambdas are not closures. An inner
{…}sees only its own parameters and globals, never the enclosing function's locals. Pass captured values in by projection:f[captured]'list. .xafter a name is dyadic apply; after a verb it is monadic "value". Prefer thevaluefunction (value x≡. x) to avoid the ambiguity.
Types and nulls
| k type | list / atom | example | null |
|---|---|---|---|
| int | `I / `i | 0 1 2, !5 | 0N |
| float | `F / `f | 1.5 2.5 | 0n |
| char | `C / `c | "abc", "x" | " " |
| symbol | `S / `s | `a`b`c | ` |
| bool | `B | 101b | — |
| dict | `m | `a`b!1 2 | — |
| table | `M | +`a`b!(1 2;3 4) | — |
@x returns the type symbol. !n gives 0..n-1 — a compact range
whose @ reads `I.
Amber stores an integer vector in the narrowest type that holds every element, so
1 2 3 is physically int8 and 1 2 3000000000 is
int64 — both are "long vectors" as far as the language is concerned. Arrays handed to
NumPy or Arrow carry the true dtype rather than being widened behind your back.
Scalar, aggregation and uniform functions
All live in amber.k. Monadic functions apply prefix (sum x); dyadic ones
use brackets (wavg[x;y]).
/ scalar (element-wise)
neg not null reciprocal sqrt floor ceiling signum abs exp log sin cos
til enlist string type key value first last reverse distinct group where
flip count mod div xbar xlog round
/ aggregation (vector -> atom)
sum prd min max avg med var dev svar sdev cov scov cor wsum wavg all any
/ uniform (vector -> vector of the same length)
sums prds mins maxs deltas ratios differ prev next
/ ordering and set operations
rank iasc idesc asc desc xrank rotate xprev in except inter union raze sublist crossThe two everyone gets backwards
wavg[sz; px] / WEIGHTS FIRST. wavg[px;sz] is a plausible wrong number.
xbar[w; x] / bucket width first: xbar[5;0 1 2 3 4 5 6 7] -> 0 0 0 0 0 5 5 5Dictionaries, tables and keyed tables
d:`a`b`c!1 2 3 / dictionary
d`b / 2
key d / `a`b`c
value d / 1 2 3
t:([]sym:`AAPL`MSFT`AAPL; px:187.3 411.2 187.4; sz:100 250 50)
count t / 3
cols t / `sym`px`sz
meta t / c | t a -- column, type, attribute
t`px / 187.3 411.2 187.4 (a column IS a vector)
t 0 / the first row, as a dictionary
kt:([sym:`AAPL`MSFT] lot:100 10) / keyed table
keys kt / `sym
xkey[`sym; t] / key an unkeyed table
unkey kt / and back
xcols[`px`sym`sz; t] / reorder columns
xasc[`sym; t] / sort ascending by a column (sets the `s attribute)
insert[`t; (`GOOG; 141.2; 300)] / append a rowA bare table at the interactive prompt auto-renders as a grid. Big tables print Q-style — the
first CROWS rows (default 20) then .. — and the cap is applied
before formatting, so previewing a million-row table is instant.
qSQL — select, exec, update, delete
At the prompt, type the query. Bare column names like wavg[sz;px] just work.
select from trades where px>150
select sym, px from trades where sym in `AAPL`MSFT
select last px by sym from trades
select vwap:wavg[sz;px], n:#px by sym from trades where px>0
select vwap:wavg[sz;px] by time:1m xbar time from trades
exec px from trades where sym=`AAPL / a plain vector, not a table
update px:px*1.01 from trades where sym=`AAPL
delete from trades where sz<100Inside a .k script the line-level rewrite does not run, so use the string form or the
functional forms directly:
sel"select vwap:wavg[sz;px] by sym from trades" / string form; also exq"…" upd"…" del"…"
qwhere[t; …] / filter
qselect[t; …] / project
qby[t; `sym; (,`vwap)!,{wavg[x`sz;x`px]}] / group + aggregate
xgroup[`sym; t] / group into a keyed table of lists
ungroup t / and flatten it back
fby[…] / aggregate-by within a where clauseqby[t; keys; aggs] takes a table, a symbol (or symbol vector) of grouping columns,
and a dictionary mapping output names to functions. Each function receives the sub-table for
one group, so {wavg[x`sz;x`px]} reads two columns of that group and returns one
number.
Joins
| Verb | What it does |
|---|---|
lj[t;kt] | left join a table against a keyed table |
ij[t;kt] | inner join — rows present in both |
uj[x;y] | union join — the union of columns and rows |
pj[t;kt] | plus join — numeric columns are added, not replaced |
ej[c;x;y] | equi join on the named columns |
aj[c;x;y] · aj0[c;x;y] | as-of — for each left row, the most recent right row at or before it |
wj[w;c;x;(y;a)] | window — aggregate right rows inside a window around each left row |
asof[x;y] | the scalar as-of lookup |
As-of join
The join every tick shop needs, and the one Amber implements natively in C: aj
matches each trade to its most recent quote with a branch-free lower_bound
binary search over each symbol group's sorted nanosecond timestamp slice. The pure-K reference
(ajmK) is kept alongside it.
trade:([]sym:`a`b`a; time:3 4 9; px:100 200 300)
quote:([]sym:`a`a`b`a; time:1 5 2 8; bid:10 11 20 12)
aj[`sym`time; trade; quote]
/ sym time px bid
/ ------------------
/ a 3 100 10
/ b 4 200 20
/ a 9 300 12
m:aj[`sym`time; trades; quotes] / 5M trades against 10M quotes: ~704 msThe kernel needs rows sorted by (sym, time) with the `s attribute
stamped on time, so it binary-searches instead of scanning. xasc sets the
attribute for you; meta shows it in the a column. Correct on 64-bit ns
timestamps, empty groups, and no-match rows (which become null).
Window join
/ aggregate quotes inside [-1s, +1s] around every trade
w:(-1000000000 1000000000)+\:trades`time
wj[w; `sym`time; trades; (quotes; (`mbid`mask)!({max x};{min x}))]See examples/wj.k for a full walkthrough.
Attributes — the C-level change
All four kdb-style attributes are implemented in C. `at reads them; meta
shows them; asc and xasc set `s for you.
| Attribute | Set with | Effect |
|---|---|---|
`s sorted | `sa v · asc | ? (find) becomes a kernel binary search — O(log n) |
`u unique | `ua v | asserts distinctness; lookups skip duplicate handling |
`p parted | `pa v | equal values are contiguous; O(log n) group slicing |
`g grouped | `ga v | a group index — O(1) per-symbol slicing |
v:asc 2000000?1000000000 / asc sets `s
`at v / `s
v ? 12345 67890 / O(log n) — see bench.k| rows | linear scan | binary (`s) | speedup |
|---|---|---|---|
| 100 k | 87 ms | 0.6 ms | 141× |
| 500 k | 417 ms | 0.9 ms | 470× |
| 2 M | 1.73 s | 1.4 ms | 1244× |
| 5 M | 4.23 s | 1.9 ms | 2261× |
Results are identical; only the time differs.
Strings
lower upper ltrim rtrim trim ss ssr sv vs like lk1ssr["abcabc"; "b"; "X"] / "aXcaXc"
vs[","; "a,b,c"] / ("a";"b";"c")
sv[","; ("a";"b";"c")] / "a,b,c"
like["AAPL"; "AA*"] / 1bBinary serialization — -8! and -9!
-8!x encodes any K value to a byte vector; -9!y decodes it back,
byte-exact including attributes, nulls and infinities. peach ships worker results over
this wire instead of formatting and reparsing text, and amber-tick's store files are
literally -8! values.
b:-8!+`a`b!(1 2 3;4 5 6) / table -> compact byte vector
(-9!b)~+`a`b!(1 2 3;4 5 6) / 1b — exactParallelism — peach
peach[f;y] is real multi-core: it forks AMBER_THREADS worker processes
(default the online CPU count, detected via sysconf), so heavy per-item work scales
across cores with no GIL and it will not oversubscribe a small box or leave a big one idle.
peach[{avg x?1.0}; 8#1000000] / 8 heavy tasks across the poolFork and IPC overhead dominates a cheap reduction — peach pays off on per-task-heavy
work, not on sum. examples/peach.k shows the crossover, and
examples/peach_verify.k checks the binary-wire round trip.
Terminal charts and grid modes
plot (14*{sin x%7}'!74;60;9) / Braille line chart
candle bars[10; select from trades where sym=`AAPL] / Unicode candlesticks
\grid clean | rounded | sharp | heavy / table frame style
CROWS:10 / preview height
COLOR:0 / disable ANSI highlighting
PREC:4 / float precisionREPL diagnostics
| Command | What it prints |
|---|---|
\v | a workspace inspector — every global as a table of Name / Type / Shape / Memory, with a recursive deep-footprint walker so nested and table sizes are real |
\ast expr | a colour-coded parse tree; nothing is executed. Leaves carry their literal type; tacit forms get explicit Hook/Fork/Projection labels |
\disasm expr | locals, constant pool and instruction stream of the real compiled bytecode, without executing it |
\trace expr | a 4-phase profiler — parse → arena → execute → format — with a bar chart and the arena's true high-water mark |
locals (0):
constants (2):
#0 -1
#1 3
bytecode (6 bytes):
0 MONAD 1
1 CONST #0
2 CONSTDYAD const#1 dyad=3
5 MONAD 0Amber's real compiler constant-folds 1+2 and 3-4, so
the constant pool holds 3 and -1, not the original literals — exactly the
kind of detail a disassembler for the real VM surfaces that a from-scratch reimplementation
would not.
Error diagnostics
By default an error prints the terse core message with a ^ caret under the failing
token. Set AMBER_DIAG=1 and the same error is additionally rendered as a
Rust-compiler-style report:
error[E0104]: Vector length mismatch
--> test.k:12:8
|
12 | prices + sizes
| ^^^^^^ ^^^^^
|
= help: Both vectors must have matching lengths for element-wise `+`.The report is rendered when the error is created, so code that catches an error with
.[f;args;handler] still sees it on stderr. Since 1.9 that is switchable at run time —
useful for anything that provokes errors on purpose:
prev:`diag 0 / suppress the report; returns the previous setting
.[{1+`a};,0;{"caught"}] / no output at all
`diag prev / restoreFinance / HFT module (fin.k)
Auto-loaded after amber.k.
gentq 100000 / generate a session: sets globals `trades` and `quotes`
genopt 2000 / random option chain into the global `options`
m:aj[`sym`time; trades; quotes] / TAQ: prevailing quote for every trade
tsign m / Lee-Ready trade sign (+1 buy / -1 sell)
effspread m / effective spread = 2|px-mid|
qby[trades;`sym; `vwap!enlist {wavg[x`sz;x`px]}]
bars[1; trades] / 1-minute OHLCV bars| Group | Functions |
|---|---|
| book | mid spread spreadbps micro imbal |
| trades | vwap twap tsign signedvol effspread notional |
| returns / vol | ret logret rvol movavg movsum movmax movmin ema rollstd |
| aggregation | bars symstats |
| index | bysym symrows gidx (O(1) per-symbol slicing) |
| generators | genopt gentq |
Extended modules
Loaded automatically by repl.k after amber.k / fin.k.
| Module | Adds |
|---|---|
std.k | vectorised moving aggregates (mcount msum mavg mprd mvar mdev mmin mmax, O(n) prefix sums), dot / mmu, parse/eval/reval, a text ser/deser round-trip, protect (like .Q.trp), typed casts, peach, ts |
sys.k | .z clocks and handlers, .Q utilities, .j JSON (j.j/j.k), a minimal .h HTML renderer, plot/candle |
hdb.k | on-disk data: dset/dget, splay/dload, partsave/partload/parts. Storage is portable Amber text read back with eval — human-readable and version-independent, not memory-mapped |
ipc.k | raw-socket messaging (hopen hclose hsend hrecv hsync) and an in-process tickerplant (u.def u.sub u.pub u.get u.end) |
temporal.k | the native date / time / timestamp layer — see Temporal mechanics |
Built-in help
\ / the menu
\q / scalars, aggregation, sets, strings
\j / tables, keyed tables, joins, qSQL
\z / temporal, bars, attributes, display
\m / the finance / HFT module
\0 \+ \' \` / the core array language and its cheat-sheet
\h / help index