ThinkScript Basics for Thinkorswim

thinkScript is the scripting language built into the thinkorswim platform, now operated by Charles Schwab, and it lets you add your own studies and strategies to the platform's several hundred built-in ones without leaving the charts. Its vocabulary is small. You define values with def, expose settings with input, draw with plot, and the platform runs your script once per bar across the chart's history, so a script that reads correctly on paper usually reads correctly on the chart. That simplicity is also the trap: it is easy to write a study that looks predictive because it quietly references bars that had not closed yet. This guide covers the editor, the syntax that matters, five corrected examples including a minimal strategy, the mistakes that make backtests lie, and how the same rule looks on Quant Charts, where Quant, our coding agent, writes the script from a description.
Key points:
- Three keywords do most of the work: def for calculations, input for user settings, plot for anything drawn; every statement ends with a semicolon.
- Studies and strategies differ: a study draws values, a strategy adds simulated orders with AddOrder and produces a report.
- Offsets read the past: close[1] is the previous bar's close; negative offsets reach into the future and belong only in strategy fills.
- Test on completed bars: a script evaluated on the forming bar changes until that bar closes, and a backtest built on it overstates its results.
Video: Building a Custom Indicator in thinkScript
Charles Schwab published this walkthrough in December 2024. It covers opening the editor, writing a first study and adding it to a chart.
The Editor and the Syntax
Opening the Editor
The thinkScript Learning Center's tutorial overview gives the route: on the Charts tab, click Studies, choose Edit Studies, and click Create below the list of studies. The editor opens with a one-line script, plot Data = close;, which draws the close price, and the platform checks and parses whatever you type before adding it to the chart as a study or a strategy. Give the script a name, save it, and it appears alongside the built-ins.
Syntax Rules
- Statements end with a semicolon. Missing one is the most common parse error.
- def declares a value computed on every bar, for example def diff = close - open;.
- input declares a user setting with a default, shown in the study's settings dialog: input length = 14;.
- plot draws a series and creates a named object whose appearance you can set: plot Line = Average(close, length); Line.SetLineWeight(2);.
- Square brackets offset in time. close[1] is the previous bar's close; close[length] is the close length bars ago. Negative offsets look forward and should appear only where the platform expects them, such as an order fill at the next bar's open.
- Comments use # for a single line.
- Reserved words read like English. The crosses reserved word, documented with its own reference page, tests whether one value moves above or below another: close crosses above Avg.
# A simple momentum study
input length = 14; # adjustable in the study settings
def momentum = close - close[length]; # change over the last N bars
plot MomentumLine = momentum;
plot Zero = 0;
MomentumLine.SetLineWeight(2);
MomentumLine.AssignValueColor(if momentum > 0 then Color.GREEN else Color.RED);
Organising a Script
Studies stay readable when they follow one order: inputs first, then calculations, then signal definitions, then plots and labels. The editor does not enforce it, but a script organised that way is easier to test, because each layer can be checked on its own. The example below follows the pattern and uses the crosses reserved word exactly as the reference page shows it.
# 1. Inputs
input fastLength = 10;
input slowLength = 30;
# 2. Calculations
def fastMA = Average(close, fastLength);
def slowMA = Average(close, slowLength);
# 3. Signals
def crossUp = fastMA crosses above slowMA;
def crossDown = fastMA crosses below slowMA;
# 4. Plots and labels
plot Fast = fastMA;
plot Slow = slowMA;
plot Up = crossUp;
plot Down = crossDown;
Up.SetPaintingStrategy(PaintingStrategy.BOOLEAN_ARROW_UP);
Down.SetPaintingStrategy(PaintingStrategy.BOOLEAN_ARROW_DOWN);
AddLabel(yes, "Fast above slow: " + (fastMA > slowMA), if fastMA > slowMA then Color.GREEN else Color.GRAY);
Building Indicators

Inputs, Built-In Functions and Conditions
Most studies are combinations of built-in functions and conditions. The reference lists the technical functions, including RSI, SimpleMovingAvg and Average, each with its own parameters, and a condition is any expression that evaluates to true or false. Conditions combine with and, or and not, and if-then-else expressions return values conditionally. A useful habit is to define every condition with def before plotting it, so the logic can be read on its own line.
input rsiLength = 14;
input oversold = 30;
input volumeMultiple = 1.5;
input volumeLength = 20;
def rsiValue = RSI(length = rsiLength);
def volumeRatio = volume / Average(volume, volumeLength);
def setup = rsiValue < oversold and volumeRatio > volumeMultiple;
plot Entry = if setup then low else Double.NaN;
Entry.SetPaintingStrategy(PaintingStrategy.ARROW_UP);
Entry.SetDefaultColor(Color.GREEN);
AddLabel(setup, "Oversold on heavy volume", Color.GREEN);
This marks bars where RSI is below the oversold input while volume runs at least one and a half times its recent average. Double.NaN suppresses the plot on bars where the condition is false, which is the standard way to draw a marker only when something happens.
Recursion and Multi-Bar Logic
Some values depend on their own previous value: a running count, a trailing stop, a highest-since-signal level. thinkScript supports that through recursion with rec, and the CompoundValue function exists to supply a starting value for the first bars where the previous value does not yet exist. The pattern below counts consecutive closes above a moving average and resets when the condition fails.
input length = 20;
def avg = Average(close, length);
rec count = if close > avg then count[1] + 1 else 0; # consecutive bars above the average
plot Streak = count;
Streak.SetPaintingStrategy(PaintingStrategy.HISTOGRAM);
AddLabel(yes, "Bars above average: " + count, if count >= 5 then Color.GREEN else Color.GRAY);
Display Settings
Every plot object accepts appearance methods: SetDefaultColor, SetLineWeight, SetPaintingStrategy (line, histogram, points, boolean arrows and more), AssignValueColor for conditional colouring, and SetStyle for dashed or dotted lines. AddLabel places text in the chart corner and takes a visibility condition, a string and a colour, which makes it a convenient readout for a study's current state. Keep the display layer separate from the logic so that changing colours never touches the rule.
From Study to Strategy
A Volume Spike Study
input volumeMultiplier = 2.0;
input lookback = 20;
def avgVolume = Average(volume, lookback);
def spike = volume > avgVolume * volumeMultiplier;
plot Spike = spike;
Spike.SetPaintingStrategy(PaintingStrategy.BOOLEAN_ARROW_UP);
Spike.SetLineWeight(3);
Spike.SetDefaultColor(Color.YELLOW);
Alert(spike, "Volume spike", Alert.BAR, Sound.Ding);
The Alert function raises a platform alert when the condition is true; Alert.BAR fires once per bar. Alerts built this way run while the chart is open and are a study feature, not a broker-side order.
A Minimal Strategy
A strategy is a script that places simulated orders with AddOrder, and thinkorswim then produces a strategy report of the trades it would have taken. Convert the crossover study into a strategy by adding two orders. The open[-1] price fills each order at the next bar's open, which is the documented way to avoid filling on the same bar that produced the signal.
input fastLength = 10;
input slowLength = 30;
input shares = 100;
def fastMA = Average(close, fastLength);
def slowMA = Average(close, slowLength);
def crossUp = fastMA crosses above slowMA;
def crossDown = fastMA crosses below slowMA;
AddOrder(OrderType.BUY_TO_OPEN, crossUp, open[-1], shares, Color.GREEN, Color.GREEN, "Long");
AddOrder(OrderType.SELL_TO_CLOSE, crossDown, open[-1], shares, Color.RED, Color.RED, "Exit");
Read the strategy report the way you would read any backtest: trade count before profit, the largest losing streak before the win rate, and the result on a period the parameters were not tuned on before anything else. A two-average crossover earns its return in trends and gives it back in ranges, and no scripting language changes that arithmetic.
Mistakes That Make Scripts Lie
| Mistake | Why it misleads | Fix |
|---|---|---|
| Acting on the forming bar | Values on bar 0 change until the bar closes; a signal can appear and vanish intrabar | Base signals on completed bars, or accept that live alerts will differ from the historical plot |
| Negative offsets in a study | close[-1] is tomorrow's close; a study that uses it looks prophetic and cannot run live | Use negative offsets only for strategy fills such as open[-1] |
| Higher-timeframe references without care | A daily value referenced on an intraday chart can reflect the day's close before the day has ended | Confirm secondary-aggregation values are taken from the completed period |
| Recursion without a seed | rec values start undefined and can propagate NaN through the whole plot | Seed with CompoundValue or a conditional first value |
| Strategy fills at the signal price | Filling at the close that produced the signal was not available in real time | Fill at open[-1] and include commission in the report settings |
| Optimising on the whole chart | Parameters tuned on all the data cannot be tested on any of it | Tune on one period, read the report on another |
The Same Rule on Quant Charts
thinkScript runs only inside thinkorswim. On Quant Charts the equivalent workflow starts from plain language: describe the crossover, the RSI-and-volume filter or the streak counter to Quant, and it writes the script in Pine Script, plots it on the active chart, and lets you open Code to read it before you click Run. For a strategy, Quant adds entries and exits and Quant Charts shows a Backtest Summary with net profit, trade count, win rate, maximum drawdown and profit factor, with commission and slippage set in the strategy properties. The Making Strategies with Quant guide shows the workflow, and the native backtest guide explains the report.
| Concept | thinkScript | Pine Script on Quant Charts |
|---|---|---|
| Declare a value | def x = close - open; | x = close - open |
| User setting | input length = 14; | length = input.int(14) |
| Previous bar | close[1] | close[1] |
| Crossover | a crosses above b | ta.crossover(a, b) |
| Draw | plot Line = value; | plot(value) |
| Recursion | rec count = count[1] + 1; | var count = 0, then count += 1 |
| Simulated order | AddOrder(OrderType.BUY_TO_OPEN, cond, open[-1], ...) | strategy.entry("Long", strategy.long) when cond |
| Who writes it | You, in the thinkorswim editor | Quant, from your description; editable in Code |
Two notes for traders who work on both platforms. The Library publishes the Pine Script® source of its indicators, and PineTS, LuxAlgo's TypeScript implementation of the Pine Script® language, lets that logic run outside TradingView; porting to thinkScript remains a manual translation, and the concept table above is the map. The LuxAlgo platform does not place orders for you; it is a charting platform. Our guides on writing Pine Script® indicators and NinjaScript basics cover the neighbouring languages.
Conclusion
thinkScript is small enough to learn in an afternoon and expressive enough to build most of what discretionary traders want on a chart: momentum readouts, crossover arrows, volume filters, streak counters and simple strategies with a report. The language will not stop you from writing a study that peeks at the future or a strategy that fills at prices it could not have had, so the discipline lives in the habits: completed bars, next-bar fills, seeded recursion and a report read on data the parameters never saw. Learn those and the rest is vocabulary. The same habits carry over unchanged to Quant Charts, where Quant supplies the vocabulary for you.
Key Takeaways
- def, input, plot. Three keywords, semicolons everywhere, offsets in square brackets.
- Studies draw, strategies order. AddOrder with open[-1] fills turns a study into a testable strategy with a report.
- Completed bars only. Values on the forming bar move until it closes; negative offsets belong to fills, not signals.
- Seed recursion. rec with CompoundValue keeps running values from starting undefined.
- Same rule, written for you. Describe it to Quant on Quant Charts, inspect the Pine Script in Code, click Run.
FAQs
What is thinkScript?
thinkScript is the programming language embedded in the thinkorswim platform, now operated by Charles Schwab. It lets you write your own studies and strategies alongside the platform's several hundred built-in ones, referencing their values, drawing plots and labels, raising alerts and, in strategies, placing simulated orders that produce a report.
How do I open the thinkScript editor?
On the Charts tab, click Studies, choose Edit Studies, and click Create below the list of studies. The editor opens with the one-line script plot Data = close;, which draws the close price. Type your script, and the platform parses it and adds it to the chart as a study or strategy once it is valid.
What is the difference between a study and a strategy in thinkScript?
A study computes and draws values, labels and alerts. A strategy also calls AddOrder to place simulated buy and sell orders, and thinkorswim produces a strategy report of the resulting trades. The usual fill price is open[-1], the next bar's open, so that orders are not filled on the bar that produced the signal.
Does thinkScript repaint?
A study can appear to repaint when it reads the forming bar, whose values change until the bar closes, or when it uses negative offsets or higher-timeframe values that were not yet final at the time. Base signals on completed bars, reserve negative offsets for strategy fills, and confirm secondary-aggregation values come from completed periods.
Can I backtest a thinkScript study?
Only by turning it into a strategy: add AddOrder calls for the entry and exit conditions and read the strategy report. Tune parameters on one period and read the report on another, include commission, and treat a small trade count as noise. A study alone draws values; it does not produce trade statistics.
Can I use the same logic on Quant Charts?
Yes. Describe the rule to Quant on Quant Charts and it writes the script in Pine Script, which you can inspect in Code and run with Run; strategies produce a Backtest Summary with commission and slippage set in the properties. thinkScript itself runs only in thinkorswim, so moving a script between the two is a translation, and the concept table in this article maps the common constructs.
References
LuxAlgo Resources
- Quant Charts
- LuxAlgo Quant
- Making Strategies with Quant
- Native Backtest Guide
- PineTS Documentation
- Moving Average Indicator
- How to Write Pine Script for Trading Indicators
- NinjaScript Basics for Custom Indicators
- How to Backtest Custom Indicators in MetaTrader
External Resources
Read next