Steven Imrich

HomeTrading platforms → What causes TooComplexException in thinkorswim and how do I fix it?

What causes TooComplexException in thinkorswim and how do I fix it?

Updated August 27, 2026

Short answer

TooComplexException means your script is syntactically fine but thinkorswim has decided it costs too much to run, usually in a scan, watchlist column, alert or conditional order rather than on a chart. The full text is "the complexity of the expression suggests that it may not be reliable with real-time data". Common triggers are long if/else chains, the same expensive function called repeatedly instead of stored in a variable, big lookback lengths, and secondary aggregation periods. There's no published limit number, so the practical fix is to cache repeated expressions, shorten lookbacks, and split the script until it runs.

First, it is not a bug in your code

The full string is com.devexperts.tos.thinkscript.runtime.TooComplexException: The complexity of the expression suggests that it may not be reliable with real-time data. Read that second half again. It’s not saying your script is wrong. It’s saying thinkorswim looked at what you’re asking for, estimated the cost, and decided it won’t run it against a live feed.

The same script often runs fine as a chart study and blows up the moment you paste it into a scan filter or a watchlist column. That’s the tell. Charts get a much longer leash than anything that runs across hundreds or thousands of symbols at once. Long-time forum members describe it as a deliberate throttle to keep heavy scripts from hammering the servers, which matches the behaviour even though I can’t point you at an official statement saying so.

What actually sets it off

In rough order of how often I see it cause the error:

  • Long chains of if ... then ... else if .... Twenty branches is usually fine, sixty is usually not. It compounds fast when each branch calls a function.
  • The same expression evaluated over and over. Writing Average(close, 50) in eight places computes a fifty period average eight times. thinkScript doesn’t dedupe that for you.
  • Large lookback lengths. Highest(high, 500) costs a lot more than Highest(high, 50). A 252 day anything in a scan is asking for trouble.
  • Secondary aggregation periods. Every period = AggregationPeriod.X pulls a second data series. Two or three of them in one script is often enough on its own. In scans and columns they’re usually rejected outright anyway, which is a separate error.
  • fold loops. A fold of 100 iterations where each iteration touches historical data is expensive per bar, and it runs on every bar.
  • Recursive defs with long memory. def x = if cond then x[1] + 1 else 0; is cheap. The same thing chained through five other recursive defs is not.
  • Everything else you have loaded. Several people report the error appearing on a script that worked yesterday, and going away after they removed unrelated studies from the same chart or column set.

The edits that actually get you under the limit

Start with caching, because it’s free and it usually does it. Anywhere you use the same expression more than once, store it:

# before
plot sig = close > Average(close, 50) and close[1] <= Average(close, 50)
           and Average(close, 50) > Average(close, 50)[5];

# after
def ma = Average(close, 50);
plot sig = close > ma and close[1] <= ma and ma > ma[5];

Same result, one moving average instead of four. This is the single highest-yield change and it’s the one people skip because it feels cosmetic. It isn’t.

Next, shorten the lookbacks. Ask whether you genuinely need 200 bars of history to answer the question. A lot of scan filters written as “highest high of the last year” work identically as “highest high of the last 60 days” for the symbols that would ever pass.

Then, strip the chart-only code. Scripts get converted from studies to columns without editing, and all the AddChartBubble, AddVerticalLine, AddCloud, Alert and AssignPriceColor lines come along for the ride. None of them do anything in a column or a scan and they all cost. Delete them.

Then, kill the redundant aggregations. If your script asks for daily closes three times, ask once and reuse the def.

Then, split it. This is the one people resist and it works better than anything else. A scan does not have to be one filter. Add filter, Study, one condition. Add filter, Study, second condition. thinkorswim ANDs them together and each one gets its own complexity budget. So a monster script that won’t run as one filter frequently runs perfectly as three. Same trick with columns: two narrower columns instead of one that does everything.

If you’re stuck with a fold, see whether a built-in does the same job. fold i = 0 to 20 with p do p + GetValue(volume, i) is just Sum(volume, 20), and the built-in is dramatically cheaper.

Finding your own ceiling by bisecting

Since nobody publishes the limit, and it plainly isn’t a simple line count, the only reliable way to know where you stand is to find the edge yourself.

Comment out roughly half your script, replace whatever you removed with a constant so it still compiles, and run it. If it runs, the problem is in the half you removed. Cut that half in half and repeat. Four or five rounds and you’ll be looking at the two or three lines that cost the most, and they’re usually not the ones you’d have guessed. In my experience it’s almost always either a lookback number or a function being recomputed inside a condition.

Keep the passing version saved before each round. It’s easy to end up with a broken script and no memory of what the last working one looked like.

What to do if it just won’t fit

Sometimes the honest answer is that the thing you want doesn’t belong in a scan. If it needs three timeframes and a 200 day lookback, run a wide, cheap scan first to get from 8,000 symbols down to 40, save that as a watchlist, and put the expensive script on a chart or column against those 40. Nobody enjoys the two step version but it runs, and it runs consistently, which the one step version wasn’t going to do.

And if the scan runs clean but comes back empty instead of erroring, that’s a different problem entirely, covered in why thinkorswim scans return no results.

Related questions