You should check the execution plan for your query. You can see it by adding EXPLAIN clause at the beginning of the query text:
EXPLAIN SELECT
timestamp,
first(price) AS open,
last(price) AS close,
min(price),
max(price),
sum(amount) AS volume
FROM trades
WHERE symbol = 'BTC-USD' AND timestamp > dateadd('d', -1, now())
SAMPLE BY 15m ALIGN TO CALENDAR;
If you see Async ... nodes in the output, e.g. Async JIT Group By or Async JIT Filter, it means that the corresponding parts of the query execution plan run in parallel, i.e. on multiple threads.
Note that by default, parallel execution is disabled on machines with less than 4 cores.
Also, in many cases, if your query runs on a single thread, you can rewrite it in a parallel-friendly way. Sometimes this can be as simple as moving the aggregates and the filter from the outer query for the sub-query.
Here is a simple example:
SELECT symbol, sum(price)
FROM (
SELECT symbol, price FROM trades
UNION ALL
SELECT symbol, price FROM trades
)
WHERE symbol = 'BTC-USD';
This query will run single-threaded since the GROUP BY and the filter is applied to a UNION ALL sub-query. But if you rewrite it in the following way, both parts of the sub-query will be handled on multiple threads:
SELECT symbol, sum(price)
FROM (
SELECT symbol, sum(price) FROM trades
WHERE symbol = 'BTC-USD'
UNION ALL
SELECT symbol, sum(price) FROM trades
WHERE symbol = 'BTC-USD'
);