Steven Imrich

HomeTrading platforms → How do I color-code a custom watchlist column in thinkorswim?

How do I color-code a custom watchlist column in thinkorswim?

Updated August 27, 2026

Short answer

Use AssignBackgroundColor(if condition then Color.GREEN else Color.GRAY) to colour the cell itself, and plotName.AssignValueColor(...) to colour the number inside it. The catch is that the script has to actually plot a value: a column made only of def lines produces an empty cell and no colour at all. For colours outside the 25 built-in constants, use CreateColor(red, green, blue), which does accept calculated values, so a smooth green-to-red scale is possible.

The two functions do different jobs and only one of them reliably shows

AssignBackgroundColor(color) fills the cell. Official docs are explicit that in a custom quote script it colours the quote cell background, and this is the one that works everywhere, every time.

AssignValueColor(color) is a plot method, so you call it as MyPlot.AssignValueColor(...). The docs say it sets the colour of the quote value, meaning the number itself. In practice a lot of people find that numeric columns render their text black no matter what they pass. If that’s what you see, don’t fight it. Set the background and set the value colour to Color.BLACK or Color.WHITE so the number stays legible against whatever background you chose. That’s the pattern in almost every column that’s actually in use.

There’s also AssignPriceColor, which colours candles on a chart. It does nothing in a column. Scripts get pasted from chart studies into columns all the time and this is one of the lines that quietly stops doing anything.

The gotcha that wastes an afternoon

A watchlist column has to produce an output. If your script is all def lines and one AssignBackgroundColor, you get a blank uncoloured cell and no error. thinkorswim isn’t telling you anything is wrong, because nothing is wrong, there’s just nothing to show.

So every column script needs either a plot or an AddLabel. If you only care about the colour and not the number, plot something trivial and let the background do the talking:

plot x = if close > Average(close, 20) then 1 else 0;
x.AssignValueColor(Color.BLACK);
AssignBackgroundColor(if x == 1 then Color.DARK_GREEN else Color.DARK_RED);

Green when price is above its 20 period average, red when it isn’t. The 1 and 0 sitting in the cell are ugly but they’re what makes the colour legal. Some people plot a blank-looking value instead, but a 1/0 also lets you sort the column, which turns out to be more useful than it sounds.

Remember the aggregation button next to the column name decides what “20 period” means here. On D that’s 20 days, on 5m it’s 100 minutes.

Three bands, using a def for readability

input length = 14;
plot RSIval = RSI(length = length);
RSIval.AssignValueColor(Color.BLACK);

def hot  = RSIval >= 70;
def cold = RSIval <= 30;

AssignBackgroundColor(
    if IsNaN(RSIval) then Color.GRAY
    else if hot  then Color.RED
    else if cold then Color.GREEN
    else Color.LIGHT_GRAY);

Two things to steal from that. The IsNaN branch first, so symbols without enough history get grey instead of falling through into a colour that looks like a reading. And naming the conditions as def, which costs nothing and saves you when the nesting gets four levels deep.

Long chains of if are also one of the things that eventually produces TooComplexException, so pulling conditions into named defs is doing double duty.

The 25 colour names, and everything else

thinkorswim ships exactly 25 Color constants: BLACK, BLUE, CURRENT, CYAN, DARK_GRAY, DARK_GREEN, DARK_ORANGE, DARK_RED, DOWNTICK, GRAY, GREEN, LIGHT_GRAY, LIGHT_GREEN, LIGHT_ORANGE, LIGHT_RED, LIME, MAGENTA, ORANGE, PINK, PLUM, RED, UPTICK, VIOLET, WHITE, YELLOW.

Color.CURRENT is the useful one nobody notices. It means “leave this cell alone”, so you can colour only the extremes and let everything else keep the watchlist’s normal background. On a dark theme that reads far better than painting every row grey.

For anything else, CreateColor(red, green, blue) with values 0 to 255. DefineGlobalColor does not work in watchlist columns, so don’t bother trying to make your columns respect a theme.

A graded scale, green through red

The nice thing about CreateColor is that it takes calculated values, not just literals. So you don’t need twelve nested if branches to get a gradient, you just do the arithmetic.

input length = 14;
plot RSIval = RSI(length = length);
RSIval.AssignValueColor(Color.BLACK);

# clamp to 0..1 first, otherwise an out-of-range RGB just gets ignored
def pct   = Max(0, Min(100, RSIval)) / 100;
def r     = Round(255 * pct, 0);
def g     = Round(255 * (1 - pct), 0);

AssignBackgroundColor(if IsNaN(RSIval) then Color.GRAY else CreateColor(r, g, 40));

RSI 0 comes out green, RSI 100 comes out red, and everything in between slides smoothly. The clamp matters more than it looks: feed CreateColor a value above 255 or below 0 and you get something unpredictable rather than an error, which is a miserable thing to debug.

Swap RSI for whatever you like. The same three lines work on a relative volume ratio if you divide by the top of the range you care about. There’s a full relative volume column here that plugs straight into this.

If you want coloured text specifically

You have to give up the numeric column. AddLabel(yes, text, color) renders coloured text in the cell:

def up = close > close[1];
AddLabel(yes, if up then "UP" else "DN", if up then Color.GREEN else Color.RED);

That works fine. The price is that the column now sorts alphabetically, so if you put numbers in there with AsText(), 10 will sort above 9. For a column you sort on, background colour is the right answer. For a column you just glance at, labels are nicer.

When the colour still doesn’t appear

Check that there’s a plot or a label. Then check whether the condition is true on the last bar, since that’s the only bar a column ever shows, and a condition that was true this morning is not going to colour anything now. That last one bites people constantly, and it’s the same misunderstanding that makes scans return stale hits, which is worth reading about in within vs offset.

Related questions