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)

Five rules that will bite you exactly once each
  • Dyadic library functions take brackets, not infix. Amber does not allow a user-defined function to be applied infix — x f y parses as two nouns. Write lj[t;kt], in[x;y], except[a;b], xasc[`sym;t]. Built-in verbs (+ - * % ! & | < > = ~ , ^ # _ $ ? @ .) are infix as usual.
  • No >= or <=. Write ~a<b for a>=b and ~a>b for a<=b.
  • Symbols cannot contain _. `a_b is 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.
  • .x after a name is dyadic apply; after a verb it is monadic "value". Prefer the value function (value x. x) to avoid the ambiguity.

Types and nulls

k typelist / atomexamplenull
int`I / `i0 1 2, !50N
float`F / `f1.5 2.50n
char`C / `c"abc", "x"" "
symbol`S / `s`a`b`c`
bool`B101b
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.

Integer width is real, not hidden

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 cross

The two everyone gets backwards

amber>
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 5

Dictionaries, tables and keyed tables

amber>
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 row

A 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.

amber>
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<100

Inside a .k script the line-level rewrite does not run, so use the string form or the functional forms directly:

script.k
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 clause
A grouped aggregate, spelled out

qby[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

VerbWhat 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.

amber>
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 ms
Layout is what makes it fast

The 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

amber>
/ 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.

AttributeSet withEffect
`s sorted`sa v · asc? (find) becomes a kernel binary search — O(log n)
`u unique`ua vasserts distinctness; lookups skip duplicate handling
`p parted`pa vequal values are contiguous; O(log n) group slicing
`g grouped`ga va group index — O(1) per-symbol slicing
amber>
v:asc 2000000?1000000000      / asc sets `s
`at v                         / `s
v ? 12345 67890               / O(log n) — see bench.k
rowslinear scanbinary (`s)speedup
100 k87 ms0.6 ms141×
500 k417 ms0.9 ms470×
2 M1.73 s1.4 ms1244×
5 M4.23 s1.9 ms2261×

Results are identical; only the time differs.

Strings

lower upper ltrim rtrim trim ss ssr sv vs like lk1
amber>
ssr["abcabc"; "b"; "X"]      / "aXcaXc"
vs[","; "a,b,c"]             / ("a";"b";"c")
sv[","; ("a";"b";"c")]       / "a,b,c"
like["AAPL"; "AA*"]          / 1b

Binary 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.

amber>
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 — exact

Parallelism — 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.

amber>
peach[{avg x?1.0}; 8#1000000]         / 8 heavy tasks across the pool
Where peach earns its keep

Fork 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

amber>
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 precision

REPL diagnostics

CommandWhat it prints
\va 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 expra colour-coded parse tree; nothing is executed. Leaves carry their literal type; tacit forms get explicit Hook/Fork/Projection labels
\disasm exprlocals, constant pool and instruction stream of the real compiled bytecode, without executing it
\trace expra 4-phase profiler — parse → arena → execute → format — with a bar chart and the arena's true high-water mark
\disasm (1+2)*3-4
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    0

Amber'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:

amber>
prev:`diag 0                    / suppress the report; returns the previous setting
.[{1+`a};,0;{"caught"}]         / no output at all
`diag prev                      / restore

Finance / HFT module (fin.k)

Auto-loaded after amber.k.

amber>
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
GroupFunctions
bookmid spread spreadbps micro imbal
tradesvwap twap tsign signedvol effspread notional
returns / volret logret rvol movavg movsum movmax movmin ema rollstd
aggregationbars symstats
indexbysym symrows gidx (O(1) per-symbol slicing)
generatorsgenopt gentq

Extended modules

Loaded automatically by repl.k after amber.k / fin.k.

ModuleAdds
std.kvectorised 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.kon-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.kraw-socket messaging (hopen hclose hsend hrecv hsync) and an in-process tickerplant (u.def u.sub u.pub u.get u.end)
temporal.kthe native date / time / timestamp layer — see Temporal mechanics

Built-in help

amber>
\            / 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