Query backtest data with SQL (auto-uses Parquet when available)
query_csvExecute a SQL query on a backtest artifact using DuckDB. backtestId must be the actual backtest run id already discovered from list_backtests or used with get_backtest_artifact; never pass a strategy id, aiStrategyId, revisionId, browser URL revisionId, conversation id, or placeholder such as . Use the same backtestId for every artifact query in a single-run answer. Do not mix stats/trades/agent_detail/log evidence from different backtest ids unless the user asked to compare multiple runs, requested all-runs analysis, or the current id is inaccessible and you say so. Before querying trades.csv, indicators.csv, stats.csv, agent_detail.parquet, or agent memory Parquet artifacts for completed-run analysis, call get_backtest_artifact for that artifact first; list_backtests(includeArtifacts=true) is useful discovery, but it is not a substitute for the explicit artifact availability/schema step. Running backtests often have no tabular artifacts yet. Runtime logs use get_backtest_logs, not query_csv. SQL SCOPE: Only the selected artifact is available as table data. Do not invent tables or read_parquet/read_csv paths for another artifact. Query artifacts separately or use run_data_analysis with declared owned datasets for cross-artifact comparisons. TIMESTAMPS: Use the actual csvSchema types. TIMESTAMP_NS is timezone-naive; TIMESTAMPTZ is timezone-aware. Match boundary literals to the column type. Cross-timezone comparisons require verified timezone provenance and explicit normalization, not guessed UTC or stripped offsets. IMPORTANT: This tool automatically uses the Parquet version of artifacts when available for much faster queries. BATCHING: queries: [{name, sql}, ...] can inspect several bounded views of one tabular artifact. Runtime logs are available through get_backtest_logs with query/queries/diagnosticPreset/contextLines. When you report results to the user, tell them whether Parquet or CSV format was used (shown in the response). Use query_csv for bounded read-only SQL views and aggregates from one saved tabular artifact. For full-ledger calculations, indicators or charts, run_data_analysis can read owned backtest::artifact:trades, :stats and :indicators datasets through declared internalData capabilities. Python can compare multiple owned backtests, preserving each run and date window. Use this for queryable CSV/Parquet artifacts: trades.csv, indicators.csv, stats.csv, agent_detail.parquet, memory_events.parquet, memory_retrievals.parquet, memory_state.parquet. For trades.csv/trades.parquet, remember the file can contain non-fill event rows such as submitted orders, canceled orders, position summaries, cash events, and readiness rows. For user questions like 'what trades', 'all trades', or trade reasons, a broad schema/context query is not enough: run a second query with a WHERE filter for filled status or nonzero filled_quantity, and base the filled-trade list only on that filtered result. Keep cash events and non-fill events separate. For runtime backtest diagnostics, get_backtest_logs is the log evidence tool and supports batched regex searches with context. Do not try to query runtime logs as artifacts. File Search (RAG) is NOT reliable for aggregations because it only returns partial chunks. query_csv gives you EXACT results for COUNT, SUM, AVG, GROUP BY, etc. WORKFLOW: First call get_backtest_artifact to learn the column names (csvSchema) and check nullCount for each column. Then call THIS TOOL (query_csv) to actually get the data values. Use 'data' as the table name. SAFETY: query results are bounded. Long text cells can be shortened and rows can be omitted with payloadTruncated=true; if that happens, rerun a narrower query with fewer columns, lower LIMIT, WHERE filters, or LEFT(long_column, N). For agent_detail.parquet or agent_detail.csv, start with aggregate queries plus at least one ordered timeline/excerpt query with LIMIT. When using batched queries, name them agent_detail_counts and agent_detail_timeline so the evidence is unambiguous. Aggregate counts alone are not enough to answer what the agent was doing. Never raw-select full prompt/context/call text columns such as effective_system_prompt, base_system_prompt, runtime_context_text, context_text, memory_state_text, event_payload_json, event_text, summary, or final_text without LEFT(..., N). Aggregating a long text column does not make it safe: do not use MAX(summary), MAX(final_text), ANY_VALUE(summary), or ANY_VALUE(final_text) unless the aggregate is wrapped in LEFT(..., N). TIMESTAMPS: Timestamp fields are returned as ISO 8601 strings (e.g., '2024-01-15T09:30:45.123456') in UTC. Check nullCount in csvSchema - some columns may have NULLs for certain row types. For indicators.csv: the 'timestamp' column (Unix epoch DOUBLE) may be NULL for price/OHLC data (e.g., MNQ) - use epoch(datetime) instead for those rows. EXAMPLE QUERIES:
Total trades: SELECT COUNT(*) FROM data
Filled trades only: SELECT * FROM data WHERE lower(coalesce(status, '')) = 'filled' OR coalesce(filled_quantity, 0) <> 0 ORDER BY time LIMIT 100
Win rate: SELECT COUNT(*) as total, SUM(CASE WHEN profit > 0 THEN 1 ELSE 0 END) as winners FROM data
By symbol, only after schema confirms symbol: SELECT symbol, COUNT(*) FROM data GROUP BY symbol
Indicator values: SELECT * FROM data LIMIT 50
AI agent event counts: queries=[{name:'agent_detail_counts',sql:'SELECT event_kind, tool_name, COUNT(*) AS events FROM data GROUP BY event_kind, tool_name ORDER BY events DESC'}]
AI agent timeline: queries=[{name:'agent_detail_timeline',sql:'SELECT call_index, event_index, event_kind, tool_name, LEFT(event_text, 500) AS event_text, call_input_tokens, call_output_tokens, call_cached_input_tokens, call_thinking_tokens, call_latency_ms FROM data ORDER BY call_index, event_index LIMIT 50'}]
AI agent call summaries: SELECT call_index, MIN(timestamp) AS first_ts, MAX(timestamp) AS last_ts, MAX(agent_name) AS agent_name, LEFT(MAX(summary), 500) AS summary_excerpt, LEFT(MAX(final_text), 500) AS final_text_excerpt, MAX(call_input_tokens) AS input_tokens, MAX(call_output_tokens) AS output_tokens, MAX(call_latency_ms) AS latency_ms FROM data GROUP BY call_index ORDER BY call_index LIMIT 50
AI agent model usage: SELECT COUNT(DISTINCT call_index) AS calls, MAX(call_input_tokens) AS max_input_tokens, MAX(call_output_tokens) AS max_output_tokens, MAX(call_latency_ms) AS max_latency_ms FROM data
Price data (when timestamp is NULL): SELECT * FROM data WHERE name = 'MNQ' AND epoch(datetime) BETWEEN start AND end Results are limited to 100 rows for raw data queries (aggregations are not limited).
Input Schema
| Name | Required | Description | Default |
|---|---|---|---|
| sql | No | Single read-only SQL over the selected artifact as table "data". Example: SELECT COUNT(*) FROM data. Inspect its schema before referencing other columns. Pass either sql or queries, not both. | |
| queries | No | Batch several named read-only SQL queries against the same tabular artifact in one tool call. Strongly prefer this for trades, stats, agent_detail, indicators, and other tabular evidence when diagnosis needs multiple evidence angles. Runtime logs use get_backtest_logs with query/queries/diagnosticPreset. For completed-backtest improvement/tuning advice, a successful query_csv result should be followed by get_backtest_logs unless that log evidence was already checked. | |
| backtestId | Yes | Actual backtest run UUID. Use the same backtest id previously used for get_backtest_artifact. Do not use a strategy id, aiStrategyId, revisionId, browser URL revisionId, conversation id, or a placeholder such as <unknown>. | |
| artifactType | Yes | The tabular artifact type to query: trades.csv, stats.csv, indicators.csv, agent_detail.parquet, memory_events.parquet, memory_retrievals.parquet, or memory_state.parquet. Runtime logs are not query_csv artifacts; use get_backtest_logs. For "improve/tune/make this backtest perform better" questions, query_csv on stats/trades/agent_detail is not enough; after tabular queries, call get_backtest_logs for errors, order/fill/position activity, and strategy/runtime lines before final advice. |