Programmable Queries#
Note
Programmable queries currently apply to a single tool: symbol universe
(search_symbol_universe).
The search_symbol_universe tool runs a OneTick query to return instruments and their
attributes. By default it runs a bundled OneTick Cloud query
(onetick_llmbase.mcp.default_queries.symbol_universe_user_query) against the
SYMBOL_UNIVERSE database. You can replace that query with your own — pointing at your own
database, tick type, columns, and filters — without forking the server, by setting the
OTP_SYMBOL_UNIVERSE_QUERY_PATH environment variable to a Python file that follows the contract below.
Tip
The quickest way to start is to copy the bundled default query module and edit it. It is
a complete, working example of the contract (filters, symbol selection, column filtering) for
the OneTick Cloud SYMBOL_UNIVERSE data, and is the file the server runs when
OTP_SYMBOL_UNIVERSE_QUERY_PATH is unset.
What your query controls — and what the server handles#
Your query file is responsible for one thing: describing which instruments/rows to return
and how to filter them. It returns an onetick.py source object.
Everything else is handled by the server around your query, so your file stays small and you do not implement it:
Handled by the server |
Notes |
|---|---|
Date range |
Derived from the |
Result limit / size |
The result count is capped (see Result size). |
Authentication |
OneTick WebAPI / auth modes ( |
Execution & timeout |
The server runs your source with |
Formatting |
Rows are serialized into the tool result returned to the model. |
Because the date window is applied by the server at execution time (it is an argument to
otp.run, not a filter inside the query graph), it is intentionally not part of your query
file. See Date range and query span for how to widen it if your data is not a daily snapshot.
Query module contract#
A query file must define two names:
SUPPORTED_FILTERS(dict)A
{filter_name: description}mapping. The descriptions are included in the tool description so the model knows which filters are available and what they mean — write them for the model to read. At runtime, only filter names listed here that also carry a value are passed tobuild_query; unknown names and empty values are dropped.build_query(otp, settings, filters)(callable)Receives:
otp— theonetick.pymodule.settings— the server settings object (read your own configuration values here, e.g. any custom fields you added to aSettingssubclass).filters— adictof the validated filters actually supplied for this call (a subset ofSUPPORTED_FILTERS).
It must return an
onetick.pysource object thatotp.run()can execute.
Example#
# my_query.py
SUPPORTED_FILTERS = {
"exchange": "Filter by Market Identifier Code (e.g. XNAS, XNYS).",
"currency": "Filter by trading currency (e.g. USD, EUR, GBP).",
}
def build_query(otp, settings, filters):
data = otp.DataSource(
db="MY_UNIVERSE_DB",
tick_type="SYMBOLS",
schema_policy="manual",
)
if "exchange" in filters:
data = data.where(otp.Column("MIC", str) == filters["exchange"])
if "currency" in filters:
data = data.where(otp.Column("CURRENCY", str) == filters["currency"])
return data
Point the server at it:
OTP_SYMBOL_UNIVERSE_QUERY_PATH=/path/to/my_query.py
For a fuller, production example — including selecting the symbol set with otp.Symbols and
merging across symbols — see the bundled default module described at the top of this page.
Enabling the tool#
Programmable queries only apply when the Query Based tool group is enabled and OneTick access is configured:
Include
queriesinLLMBASE_MCP_TOOLS(or leave the default, which enables all tool groups). See Tools.Configure OneTick authentication (
OTP_AUTH_MODEand its credentials, ornonefor an unauthenticated gateway). See OneTick WebAPI authentication.
Date range and query span#
The model chooses the date window per call through the tool’s start_date / end_date
arguments:
If neither is given, the window defaults to the previous trading day (a one-day span).
If only
start_dateis given,end_datedefaults tostart_date + 1 day.
The effective span is then capped to MAX_QUERY_SPAN_DAYS (default 1). The bundled
SYMBOL_UNIVERSE query is a point-in-time daily snapshot, so a wider range is never more
useful than a single day — it only multiplies the per-day scan and can time out. Over-wide,
inverted, or empty ranges are clamped to this span.
Important
If your custom query is not a daily snapshot and genuinely needs multi-day ranges (for
example, “instruments seen over the last 5 days”), raise MAX_QUERY_SPAN_DAYS — otherwise
requests for a wider window are silently narrowed. Set a large value to effectively disable the
cap.
Variable |
Default |
Description |
|---|---|---|
|
(unset) |
Path to your query file. Unset → the bundled default query runs. |
|
|
Maximum |
|
|
Seconds before a running query is aborted with a clear error. |
See Configuration for the full variable reference.
Result size#
The number of rows returned is clamped to an internal maximum (50), regardless of the limit
the model requests. Wide symbol rows would otherwise exceed the MCP client’s tool-result size
limit. When results are truncated, the tool tells the model to narrow the query with filters
rather than to request more rows. Design SUPPORTED_FILTERS so the model can meaningfully
narrow broad requests.
Timeout#
Each query runs under OTP_QUERY_TIMEOUT (default 120 seconds). A query that exceeds it is
aborted and returns a clear error message instead of blocking the server. Keep custom queries
selective (push filters into the query, avoid full scans) so they stay well within the timeout.
AST guardrails#
Operator-supplied query files are scanned with an AST allow-list before loading. The scan is a
guardrail against accidents, not a security sandbox — the file is treated as operator-owned
config-as-code (the same trust level as your .env or the wheel itself). The bundled default
query is trusted and is not scanned.
Allowed import roots: onetick, datetime, typing, math.
Banned calls: open, exec, eval, compile, __import__, input,
breakpoint, and any dunder attribute access.
Startup validation#
The query module is loaded and validated once at server startup, so mistakes surface immediately rather than on the first query. The server fails fast with a clear error if the file:
does not exist at
OTP_SYMBOL_UNIVERSE_QUERY_PATH,has a syntax error or fails the AST scan,
is missing
SUPPORTED_FILTERS(must be adict) orbuild_query(must be callable).