Open-Source Trading Terminal for Python
What an open-source trading terminal for Python really is: where an agentic IDE sits between a notebook, a backtester and a live broker, and what it cannot do.
An open-source trading terminal for Python is one window holding four jobs at once: an editor for strategy code, a data layer, a backtest runner, and a log pane that prints what ran. The 2026 crop of these projects adds a chat agent with tool access to that window, which is what a README means by "agentic IDE for Python strategies". The agent writes and runs the same code a person would write by hand, and it inherits every weakness in the data feed and the fill assumptions sitting underneath it.
What an open-source trading terminal for Python actually is
Four moving parts, none of them new on its own.
- A file layout. The strategy lives on disk as a Python module with a declared entry point, not as cell 14 of a notebook.
- A runner. One command turns that module into a trade list, an equity curve and a stats table.
- A tool surface for the model. The agent gets a handful of verbs: read a file, patch a file, run the backtest, read the result.
- A pane that prints the run. Timestamps, order intents, stack traces, and the diff the agent just applied.
What is new is the loop. The model proposes an edit, the runner scores it, the pane shows the score, and the model reads the score and proposes the next edit. Our walkthrough of how to backtest a trading strategy is the manual version of that cycle, and the manual version is still the one you audit.
Where it sits between a notebook, a backtester and a broker
A notebook keeps state in memory and in the order the cells ran, which is why a notebook result is hard to reproduce a month later. A terminal project moves that state into files and commands, closer to the discipline in our reproducible backtest walkthrough. A broker connection is a third thing again: a key, an order endpoint, and a reconciliation problem.
The seam that matters most is resolution. The pane plots one bar size, and fills happen at another.
| session_date | session_label | daily_range_bps | widest_minute_bps |
|---|---|---|---|
| 2026-06-01 | Jun 1 | 193.3 | 52.9 |
| 2026-06-02 | Jun 2 | 278.1 | 58.2 |
| 2026-06-03 | Jun 3 | 260.7 | 42.2 |
| 2026-06-04 | Jun 4 | 125 | 72.9 |
| 2026-06-05 | Jun 5 | 260.9 | 53.8 |
| 2026-06-08 | Jun 8 | 538.2 | 151.6 |
| 2026-06-09 | Jun 9 | 446.4 | 67.8 |
| 2026-06-10 | Jun 10 | 252.8 | 56.8 |
| 2026-06-11 | Jun 11 | 250.7 | 71.4 |
| 2026-06-12 | Jun 12 | 258.3 | 60.7 |
| 2026-06-15 | Jun 15 | 205.1 | 76.6 |
| 2026-06-16 | Jun 16 | 217.6 | 40.4 |
| 2026-06-17 | Jun 17 | 260.5 | 59.8 |
| 2026-06-18 | Jun 18 | 166.1 | 57.6 |
| 2026-06-22 | Jun 22 | 190.6 | 85.2 |
| 2026-06-23 | Jun 23 | 253.5 | 105.9 |
| 2026-06-24 | Jun 24 | 230.7 | 73.8 |
| 2026-06-25 | Jun 25 | 547 | 140.9 |
| 2026-06-26 | Jun 26 | 413.7 | 132.9 |
| 2026-06-29 | Jun 29 | 302.4 | 89.7 |
The exact SQL behind every number
SELECT
toString(bars.session) AS session_date,
formatDateTime(bars.session, '%b %e') AS session_label,
round((toFloat64(bars.high) - toFloat64(bars.low)) / toFloat64(bars.close) * 10000, 1) AS daily_range_bps,
round(mins.widest_minute_bps, 1) AS widest_minute_bps
FROM
(
SELECT date AS session, high, low, close
FROM global_markets.stocks_daily_aggs
WHERE ticker = 'AAPL'
AND date >= '2026-06-01'
AND date < '2026-07-01'
) AS bars
INNER JOIN
(
SELECT
toDate(toTimeZone(window_start, 'America/New_York')) AS session,
max((toFloat64(high) - toFloat64(low)) / toFloat64(close) * 10000) AS widest_minute_bps
FROM global_markets.delayed_stocks_minute_aggs
WHERE ticker = 'AAPL'
AND window_start >= '2026-06-01 04:00:00'
AND window_start < '2026-07-01 04:00:00'
AND (toHour(toTimeZone(window_start, 'America/New_York')) * 60
+ toMinute(toTimeZone(window_start, 'America/New_York'))) >= 570
AND (toHour(toTimeZone(window_start, 'America/New_York')) * 60
+ toMinute(toTimeZone(window_start, 'America/New_York'))) < 960
GROUP BY session
) AS mins ON mins.session = bars.session
ORDER BY bars.sessionOver 21 June 2026 sessions, Apple's full-day range measured 193.3 basis points of its close on Jun 1 and 319.5 on Jun 30. A basis point is one hundredth of one percent. Inside those same two sessions, the widest single minute covered 52.9 and 84.3 basis points. The distance between the two lines is the room a daily-bar backtest leaves to assumption. It knows the close was printed. It knows nothing about where inside the session an order would have landed. An agent iterating against daily bars is optimizing inside that gap, which is also where the trap covered in look-ahead bias in backtesting lives.
What does the terminal pane actually show?
Usually four things: a quote strip, a positions table, a run log, and a pending-intent queue the agent fills before anything is sent. The quote strip is the one readers over-trust. A bid and an ask printed to the cent look like a price you can transact at. They are the top of a queue, and their width moves on a daily rhythm.
| et_time | median_spread_bps | p95_spread_bps |
|---|---|---|
| 09:30 | 1.69 | 4.44 |
| 10:00 | 1.35 | 2.03 |
| 10:30 | 1.01 | 2.03 |
| 11:00 | 0.68 | 1.35 |
| 11:30 | 0.67 | 1.35 |
| 12:00 | 0.67 | 1.34 |
| 12:30 | 0.67 | 1.01 |
| 13:00 | 0.67 | 1.35 |
| 13:30 | 0.67 | 1.35 |
| 14:00 | 0.68 | 1.35 |
| 14:30 | 0.68 | 1.02 |
| 15:00 | 0.67 | 1.01 |
| 15:30 | 0.67 | 1.01 |
The exact SQL behind every number
SELECT
formatDateTime(toStartOfInterval(toTimeZone(sip_timestamp, 'America/New_York'), INTERVAL 30 MINUTE), '%H:%i') AS et_time,
round(quantileDeterministic(0.5)(spread_bps, toUInt64(sequence_number)), 2) AS median_spread_bps,
round(quantileDeterministic(0.95)(spread_bps, toUInt64(sequence_number)), 2) AS p95_spread_bps
FROM
(
SELECT
sip_timestamp,
sequence_number,
(toFloat64(ask_price) - toFloat64(bid_price))
/ ((toFloat64(ask_price) + toFloat64(bid_price)) / 2) * 10000 AS spread_bps
FROM global_markets.cache_stocks_quotes
WHERE ticker = 'AAPL'
AND sip_timestamp >= '2026-06-15 13:30:00'
AND sip_timestamp < '2026-06-15 20:00:00'
AND bid_price > 0
AND ask_price > bid_price
)
GROUP BY et_time
ORDER BY et_timeIn the 09:30 ET bucket on June 15, 2026, the median quoted spread on Apple measured 1.69 basis points, with the 95th percentile quote out at 4.44. By the 15:30 bucket, the median printed 0.67. Read the curve left to right and the shape is the whole lesson: a strip at 09:35 and the same strip at midday describe different liquidity, while a backtest charging the midpoint at every timestamp charges the same price in both. Where an order sits behind the quoted size is a separate estimate, broken down in how to estimate queue position.
Which parts need an API key or paid data?
Three tiers recur. Daily bars are usually free or nearly free, often adjusted and sometimes a day behind. Minute bars, quote ticks and full options chains sit on paid plans. The agent itself needs a model API key, billed per token on every iteration, so a terminal that loops ten times on one prompt carries a per-run cost no README benchmark covers.
Options panes make the tiering obvious. An implied-volatility reading is not a column anyone downloads. It is a per-contract field computed from a contract price, shipped with a convergence flag marking the solves that failed.
| dte_bucket | atm_iv_pct | contracts |
|---|---|---|
| 0-7d | 26.4 | 68 |
| 8-21d | 22.8 | 79 |
| 22-45d | 22 | 36 |
| 46-90d | 25 | 24 |
| 91d+ | 27 | 99 |
The exact SQL behind every number
SELECT
multiIf(days_to_expiry <= 7, '0-7d',
days_to_expiry <= 21, '8-21d',
days_to_expiry <= 45, '22-45d',
days_to_expiry <= 90, '46-90d',
'91d+') AS dte_bucket,
round(avg(implied_volatility) * 100, 1) AS atm_iv_pct,
count() AS contracts
FROM global_markets.options_greeks
WHERE underlying_symbol = 'AAPL'
AND date = '2026-06-15'
AND iv_converged = 1
AND volume > 0
AND days_to_expiry >= 0
AND abs(toFloat64(strike_price) / toFloat64(underlying_close) - 1) < 0.05
GROUP BY dte_bucket
ORDER BY min(days_to_expiry)Across Apple contracts struck within 5% of the underlying close on that session, the 0-7d bucket averaged 26.4% implied volatility over 68 contracts that traded, against 27% in the 91d+ bucket. That is one day, one underlying, one moneyness band. Reproducing the curve for a universe of names every day is the line where free data stops. The mechanics of the number are in how implied volatility is calculated.
No execution guarantee, and no validated fill model
Every backtest inside one of these terminals assumes a price you would have received, and most make the assumption in a single line of code. The honest version of that line is a model a reader can open: a spread cost, a size cap, a latency, a rejection path.
| symbol | median_spread_bps | p90_spread_bps | quote_millions |
|---|---|---|---|
| SPY | 0.27 | 0.4 | 0.44 |
| KO | 1.23 | 2.47 | 0.04 |
| AAPL | 1.35 | 2.03 | 0.14 |
| NVDA | 1.43 | 1.44 | 0.3 |
| MSFT | 1.76 | 3.26 | 0.05 |
| NVR | 75.75 | 92.25 | 0 |
The exact SQL behind every number
SELECT
ticker AS symbol,
round(quantileDeterministic(0.5)(spread_bps, toUInt64(sequence_number)), 2) AS median_spread_bps,
round(quantileDeterministic(0.9)(spread_bps, toUInt64(sequence_number)), 2) AS p90_spread_bps,
round(count() / 1e6, 2) AS quote_millions
FROM
(
SELECT
ticker,
sequence_number,
(toFloat64(ask_price) - toFloat64(bid_price))
/ ((toFloat64(ask_price) + toFloat64(bid_price)) / 2) * 10000 AS spread_bps
FROM global_markets.cache_stocks_quotes
WHERE ticker IN ('SPY', 'AAPL', 'MSFT', 'NVDA', 'KO', 'NVR')
AND sip_timestamp >= '2026-06-15 14:00:00'
AND sip_timestamp < '2026-06-15 14:30:00'
AND bid_price > 0
AND ask_price > bid_price
)
GROUP BY ticker
ORDER BY median_spread_bpsInside one 30 minute window, median quoted spreads across 6 large caps ran from 0.27 basis points on SPY to 75.75 on NVR, whose 90th percentile quote measured 92.25. The tightest name on the list alone printed 0.44 million quote updates in that half hour. A flat per-trade cost applied across names like these is off by more than an order of magnitude at one end of the chart. High-priced stocks with few shares changing hands quote wider in relative terms than an index ETF does, every hour of every session.
Four limits travel with this genre, and a README rarely states them. There is no execution guarantee: a broker API accepts an intent, then rejections, partial fills and halts arrive on their own schedule, a topic we cover in agentic trading bots on retail brokerages. There is no validated fill model unless the project publishes one and tests it against tick data, and backtesting in illiquid markets plus latency models in HFT backtests cover what validation involves. The demo data tier is rarely the tier a strategy needs. And early-stage project risk is real: a single-maintainer repository with no tagged release and no test suite is a prototype being adopted, not a product being bought.
How to judge the claim in the README
Pin the version before anything else. A fast-moving repository changes its entry points between Sundays, and any install line written down today drifts. Clone at a tag rather than at a branch, in the shape git clone --branch <tag> --depth 1 <repo-url>, or for a published package pip install "<package>==<tag>", and record the resolved commit beside your results. Where a project publishes no tags at all, that absence is itself a finding.
Then test the claim instead of quoting it. "Agentic IDE" asserts that the agent can edit and run a strategy end to end. Four checks settle it.
- From a fresh clone and one prompt, does it produce an equity curve using the data it ships?
- Is the fill model a readable file, or a hardcoded midpoint?
- Do the backtest path and the live path share one code path, or two that can disagree?
- When the agent is wrong, does the pane show the diff it applied and the command it ran?
A yes is a capability. An adjective is not. The same four checks are how we read an open-source TradingView optimizer or a model-facing data layer, as in market data skills for AI agents. These tools occupy different seats rather than a ranking. A notebook explores. An optimizer searches a parameter grid. A terminal agent edits and reruns. A broker bridge sends.
FAQ
What is an agentic IDE for Python strategies?
It is a code workspace where a language model holds tools for reading and patching your strategy files and running the backtest, with a pane printing each edit and each run. The strategy stays ordinary Python on disk, which separates it from a chat window that hands back a code block.
Is an open-source trading terminal for Python free to run?
The code is free. The inputs usually are not. Daily bars are often free, while minute bars, quote ticks and options chains sit on paid plans, and the agent needs its own model API key, billed per token on every iteration.
Can an open-source trading terminal place real orders?
Some connect to a broker API with keys you supply, and most ship with live trading switched off by default. A connection is not an execution guarantee: the broker accepts an intent, and rejections, partial fills and venue halts follow afterward.
How accurate are the fills in these backtesters?
As accurate as the fill model, which is frequently a midpoint assumption with a fixed cost. Measured quoted spreads vary by more than an order of magnitude across large caps in the same half hour, so one flat cost flatters some names and penalizes others.
What is the difference between a notebook and a trading terminal?
A notebook keeps state in memory and in the order the cells ran. A terminal project keeps the strategy in files and runs it with a command, which is what makes a result someone else can reproduce.
Data notes and the pinned window
Every panel here is pinned to a fixed historical window in June 2026, so the same SQL returns the same numbers years from now. The quote panels aggregate tick quotes for named tickers inside a narrow clock window, measured in basis points of the quote midpoint, with zero-priced and crossed quotes dropped. Percentiles use a deterministic estimator, so two renderings of one statistic cannot drift apart. The implied-volatility panel keeps contracts within 5% of the underlying close that traded at least once, with the vendor convergence flag set.
Every panel on this page carries the exact SQL beneath it, so open one and the measurement is visible. To check a spread, a session range or an implied-volatility curve for your own window before trusting a backtester's fill assumption, ask the question in plain English on the Strasmore terminal.