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 start_date / end_date tool arguments the model supplies at call time (see Date range and query span). Your query does not set the time window.

Result limit / size

The result count is capped (see Result size).

Authentication

OneTick WebAPI / auth modes (OTP_AUTH_MODE). See OneTick WebAPI authentication.

Execution & timeout

The server runs your source with otp.run(...) under OTP_QUERY_TIMEOUT.

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 to build_query; unknown names and empty values are dropped.

build_query(otp, settings, filters) (callable)

Receives:

  • otp — the onetick.py module.

  • settings — the server settings object (read your own configuration values here, e.g. any custom fields you added to a Settings subclass).

  • filters — a dict of the validated filters actually supplied for this call (a subset of SUPPORTED_FILTERS).

It must return an onetick.py source object that otp.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 queries in LLMBASE_MCP_TOOLS (or leave the default, which enables all tool groups). See Tools.

  • Configure OneTick authentication (OTP_AUTH_MODE and its credentials, or none for 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_date is given, end_date defaults to start_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

OTP_SYMBOL_UNIVERSE_QUERY_PATH

(unset)

Path to your query file. Unset → the bundled default query runs.

MAX_QUERY_SPAN_DAYS

1

Maximum [start, end] span (in days) the tool will query. Raise it for non-snapshot queries that need multi-day ranges.

OTP_QUERY_TIMEOUT

120

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 a dict) or build_query (must be callable).