The vocabulary#
The vocabulary is everything a campaign’s candidates may write with: the fields of its input families, the operators, and the values the operators’ parameters may take. The framework has none of its own; the config names the files:
fields: [fields/sharadar_price_volume.yaml, fields/sharadar_fundamentals.yaml]
operators: operators/kunquant_example.yaml
operator_modules: [] # modules besides KunQuant and quantlab.factor operators may come from
At start every field and operator is checked and every problem reported at once, before any LLM call: a column missing from the price dataset, a malformed unit, a name clash, a class that is not a KunQuant operator, a class or callable from a module not listed, a formula outside the syntax rules.
Field files#
One field file per input family, a group of data opened or closed as a whole: price
and volume, or fundamentals. Each field names the price dataset’s column it reads, or a
formula over the family’s earlier fields, its unit and a one-line description, which
the prompts and the reviewer read:
family: price_volume
description: adjusted daily bars
bar: true # a value missing in any column is a missing bar
fields:
- name: close
column: adjClose
unit: dollars/shares
description: closing price
- name: vwap
formula: (high + low + close) / 3.0
description: typical price of the day, (high + low + close) / 3
- name: amount
formula: vwap * volume
description: dollars traded in the day, vwap * volume
A unit is a product of powers of
dollarsandshares: a price isdollars/shares, volumeshares, a ratio1. A derived field’s unit follows from its formula.bar: truemakes a value missing in any of the family’s columns a missing bar; the NaN-preserving operator stand-ins keep it NaN.A family of periodic data may declare a period lag, which lets code write
PeriodLag(x, k): the field’s valuekreporting periods earlier, counted in reports, not trading days.
period_lag:
column: '{column}__q{k}' # the name a lag is read under
k: [1, 2, 3, 4, 8]
period:
description: one quarterly report
key: reportperiod # the column holding the period end of the report shown
days: 91.3
tolerance_days: 45
bars: 63 # the most bars one period spans, for the warm-up
The repository ships sharadar_price_volume.yaml (Sharadar SEP’s adjusted bars) and
sharadar_fundamentals.yaml (SF1 trailing-twelve-month items and DAILY valuations).
A field file with an intraday key opens an intraday family: its fields are values of
each intraday bar, which candidate code computes on and reduces to one value per session.
See intraday families.
High-frequency families#
A high-frequency family is an input family whose fields are daily statistics of a minute dataset: one value per stock and session, such as the realized volatility of the session’s minute returns or its order imbalance. The campaign computes nothing from minute bars; it reads a store of the statistics built beforehand, beside the daily prices, and candidate code applies its own windows to the daily values like to any other field. For candidates that compute on the minute bars themselves, open an intraday family instead, or beside it.
A new source takes two steps, and no change to factor-agent:
Build the daily statistics store. Compute the statistics from the minute dataset with quantlab’s session-statistic factors (
IntradayStats,PriceVolumeIntradayStats), resampled to one value per day, and save them as a factor store.Write a field file for it. Add a quantlab
FactorStoreDatasetover the store to the campaign’s price dataset, merged with the daily bars, and write a field file whose columns are the statistics’ names in the merged panel, withbar: false:
data:
prices:
name: quantlab.dataset.merged.MergedDataset
datasets:
- ... # the daily bars, as before
- name: quantlab.dataset.factor_store.FactorStoreDataset
zarr_file_path: factors/session_stats/session_stats.zarr
prefix: 'trades_' # rvol is read as trades_rvol
family: session_statistics
description: daily statistics of each session's minute trade bars
bar: false # a missing statistic is a missing value, not a missing bar
fields:
- name: rvol
column: trades_rvol
unit: 1
description: realized volatility of the session's minute returns
Two rules hold for every such store:
A store’s value for day t is known at day t’s close. The dataset shifts nothing: the value stored at day t is read beside day t’s prices, so the store must hold at day t only what that day’s session revealed by its close. Aligning the values is the store builder’s job.
A missing statistic is a missing value. A session with too few trades leaves its statistics missing, and with
bar: falsethat blanks only the candidates that read them; the same day’s prices keep their values and a price-only candidate is unaffected.
The prefix keeps two sources apart. Two stores may hold an output of the same name
(trade bars and quote bars both give an rvol); read each under its own prefix
(trades_rvol, quotes_rvol) and give each field file its own field names. Two merged
inputs that both hold a value of the same column for a stock on a day are refused when the
panel is read, so a field never silently reads the wrong source. A field file naming a
column the merged panel lacks (a stored name without its prefix, a typo) is refused at
start, before any LLM call, with every other problem of the files.
The operator file#
The operator file lists each operator candidate code may call: its KunQuant class, the
call code writes when it is not the class (such as quantlab’s NaN-preserving
quantlab.factor.kunquant_nan.Max), its signature, the kind of each input, the parameter
set each attribute must be in, its dimension rule and a description.
parameters:
window: [5, 10, 20, 40, 60, 120, 250]
constant: [0, 1.0e-12, 0.01, 0.05, 0.1, 0.5, 1, 2, 5, 10, 20, 60, 120, 250]
operators:
- name: WindowedAvg
class: KunQuant.ops.WindowedAvg
signature: "WindowedAvg(x, window)"
inputs: [series]
params: {window: window}
dimension: same
description: "mean over the last window bars"
With an intraday family open, an operator’s stages say where code may use it:
[intraday, daily], [daily] (the default when an entry names none) or, for a session
aggregation, [aggregation]. An operator whose memory has no end may not name intraday.
See the rules the Gate enforces. A daily
campaign ignores the key and its prompt lists no aggregation.
The dimension rule tells the Gate the unit of the operator’s output (same, match,
product, quotient, pure, compare, …), so it can infer a candidate’s output unit;
the reviewer reads that unit, and a factor whose value carries one (a price, a dollar
amount, a share count) needs a hypothesis about that level.
The repository’s example file also lists every other KunQuant and quantlab operator
class under excluded, with the reason; a guard test keeps it complete. Your own file
needs no such section.
Fixes#
An operator file may list fixes: a match (a regular expression a refusal’s text
matches when KunQuant turns code down) and a hint (how to write it instead). The Gate
gives a writing error every hint it matches, in file order, and the hints go into the
proposer’s fix request only.
fixes:
- match: 'Bad inputs, given'
hint: "write a number as ConstantOp(1), as in Sub(x, ConstantOp(1)), or use x - 1"
KunQuant’s “Bad inputs” names only the type of the value; the Gate adds the line of the
code and the operator given the number (on line 3 (alpha = Sub(x, 1)), input 2 of Sub is the number 1, not an operator), so the fix of the code can tell which number to change.
What code may write#
Candidate code is plain Python that must assign the factor to alpha: assignments,
arithmetic and comparisons, calls of the vocabulary’s operators, def/return,
for/while/if and with blocks over plain names and constants. It may write
+ - * /, unary minus (MulConst(x, -1)) and x ** c (Pow(x, ConstantOp(c)), when the
file registers Pow). Imports, class definitions, attribute access, names with double
underscores and any other syntax are refused before the code runs; the code then builds its
graph in a sandboxed subprocess with a time and memory limit.
A missing value stays missing: the scoring skips it. A candidate fills one
(SetInfOrNanToValue) only when the mechanism says what a missing value means, stated in
its description; coverage is measured on the graph with every fill removed, so filling a
sparse field does not raise its coverage.