We have a complicated relationship with ThinkorSwim. On one hand, it’s hands-down the most feature-rich free trading platform available in the US. The charting is elite. The options chain analysis is unmatched. The scanner alone is worth the price of admission — which is zero dollars. On the other hand, ThinkScript is… ThinkScript. A declarative language that fights you every step of the way if you’re used to writing real code in MQL4 or Python.
But here’s the thing. For US stocks and options, nothing else comes close without paying up. So after twelve years of building algos across every platform imaginable, we keep coming back to TOS for a specific set of tasks. Custom studies, strategy backtests, watchlist columns, scanner scripts, conditional order triggers — all inside one platform.
ThinkScript vs Other Languages
| Feature | ThinkScript | MQL4 | Pine Script | NinjaScript |
|---|---|---|---|---|
| Cost | Free | Free | Free/Pro | Free/Paid |
| Language Style | Declarative | Procedural | Declarative | OOP (C#) |
| Live Execution | Conditional Orders | Full EA | Webhooks | Full Strategy |
| Backtesting | Strategy tab | Strategy Tester | Built-in | Strategy Analyzer |
| Debugging | Sidebar values | Print() | pine_log() | Output window |
| Best For | US stocks/options | Forex/CFDs | All markets | Futures |
ThinkScript Basics
Two things you need to understand immediately. Everything else flows from this distinction.
Study vs Strategy
- Study: A visual indicator on your chart. It draws lines, arrows, labels — but it doesn’t simulate trades. Think of it as “look but don’t touch.”
- Strategy: This is where ThinkScript pretends to be a backtesting engine. Uses
AddOrdercalls to generate simulated entries and exits. The results are… directionally useful. Don’t bet your account on the exact numbers.
Your First Study
Classic EMA crossover. Pay attention to declare upper — miss that and your indicator shows up in its own pane like an oscillator, which is confusing when you’re plotting moving averages.
# EMA Crossover Study
# Plots fast and slow EMAs with crossover signals
declare upper; # Plot on price chart (not separate pane)
input fastLength = 21;
input slowLength = 55;
def fastEMA = ExpAverage(close, fastLength);
def slowEMA = ExpAverage(close, slowLength);
plot Fast = fastEMA;
plot Slow = slowEMA;
Fast.SetDefaultColor(Color.CYAN);
Slow.SetDefaultColor(Color.ORANGE);
# Crossover arrows
plot BuySignal = if fastEMA crosses above slowEMA
then low - ATR(14) * 0.5 else Double.NaN;
plot SellSignal = if fastEMA crosses below slowEMA
then high + ATR(14) * 0.5 else Double.NaN;
BuySignal.SetPaintingStrategy(PaintingStrategy.ARROW_UP);
BuySignal.SetDefaultColor(Color.GREEN);
BuySignal.SetLineWeight(3);
SellSignal.SetPaintingStrategy(PaintingStrategy.ARROW_DOWN);
SellSignal.SetDefaultColor(Color.RED);
SellSignal.SetLineWeight(3);
Your First Strategy
Now the fun part — turning that study into something that simulates trades. The AddOrder function is ThinkScript’s version of trade execution, except nothing actually executes. It just paints entry/exit markers on the chart and generates a P&L report.
# EMA Crossover Strategy with ATR Stops
# For backtesting in TOS Strategy tab
input fastLength = 21;
input slowLength = 55;
input atrPeriod = 14;
input atrMultSL = 1.5;
input atrMultTP = 3.0;
def fastEMA = ExpAverage(close, fastLength);
def slowEMA = ExpAverage(close, slowLength);
def atrVal = ATR(atrPeriod);
# Entry conditions
def buySignal = fastEMA crosses above slowEMA;
def sellSignal = fastEMA crosses below slowEMA;
# Long trades
AddOrder(OrderType.BUY_TO_OPEN, buySignal, close,
name = "Long Entry");
AddOrder(OrderType.SELL_TO_CLOSE, sellSignal, close,
name = "Long Exit - Signal");
AddOrder(OrderType.SELL_TO_CLOSE,
close <= EntryPrice() - atrVal * atrMultSL,
close, name = "Long SL");
AddOrder(OrderType.SELL_TO_CLOSE,
close >= EntryPrice() + atrVal * atrMultTP,
close, name = "Long TP");
# Short trades
AddOrder(OrderType.SELL_TO_OPEN, sellSignal, close,
name = "Short Entry");
AddOrder(OrderType.BUY_TO_CLOSE, buySignal, close,
name = "Short Exit - Signal");
AddOrder(OrderType.BUY_TO_CLOSE,
close >= EntryPrice() + atrVal * atrMultSL,
close, name = "Short SL");
AddOrder(OrderType.BUY_TO_CLOSE,
close <= EntryPrice() - atrVal * atrMultTP,
close, name = "Short TP");
Advanced Patterns
Multi-Timeframe Confirmation
Multi-timeframe analysis in TOS is cleaner than what you get in most platforms — one line to pull a different aggregation period.
# Check daily trend while on intraday chart
input dailyEmaLength = 50;
def dailyClose = close(period = AggregationPeriod.DAY);
def dailyEMA = ExpAverage(dailyClose, dailyEmaLength);
def dailyUptrend = dailyClose > dailyEMA;
def dailyDowntrend = dailyClose < dailyEMA;
# Only take longs when daily trend is up
def filteredBuy = fastEMA crosses above slowEMA and dailyUptrend;
Session Time Filtering
If you’re not filtering by session time, you’re backtesting fantasy trades. Pre-market and after-hours have garbage liquidity on most names.
# Trade only during regular market hours
input startHour = 0930;
input endHour = 1545;
def currentTime = SecondsFromTime(startHour) >= 0
and SecondsTillTime(endHour) >= 0;
def buyCondition = buySignal and currentTime;
Range Breakout with Visual Box
One of our favorite setups to visualize in TOS. The Asian session range breakout. This script tracks the high and low during Asia hours, then plots breakout levels for the London/NY session.
# Asian session range breakout for forex
declare upper;
input rangeStartHour = 1800; # 6 PM ET = Asia open
input rangeEndHour = 0200; # 2 AM ET = Asia close
input breakoutBuffer = 0.0005; # 5 pips for forex
# Detect range period
def inRange = if SecondsFromTime(rangeStartHour) >= 0
and SecondsTillTime(rangeEndHour) >= 0
then 1
else if SecondsTillTime(rangeStartHour) > 0
and SecondsFromTime(rangeEndHour) > 0
then 0
else 0;
# Track range high/low
def rangeHigh = if inRange and !inRange[1]
then high
else if inRange
then Max(rangeHigh[1], high)
else rangeHigh[1];
def rangeLow = if inRange and !inRange[1]
then low
else if inRange
then Min(rangeLow[1], low)
else rangeLow[1];
# Plot range levels after range closes
plot UpperLevel = if !inRange then rangeHigh + breakoutBuffer
else Double.NaN;
plot LowerLevel = if !inRange then rangeLow - breakoutBuffer
else Double.NaN;
UpperLevel.SetDefaultColor(Color.GREEN);
LowerLevel.SetDefaultColor(Color.RED);
# Breakout signals
def breakoutLong = !inRange and close > rangeHigh + breakoutBuffer
and close[1] <= rangeHigh + breakoutBuffer;
def breakoutShort = !inRange and close < rangeLow - breakoutBuffer
and close[1] >= rangeLow - breakoutBuffer;
Custom Watchlist Column
This right here is one of TOS’s killer features that people sleep on. Custom watchlist columns. You can run ThinkScript on every single ticker in your watchlist simultaneously. One column script scanning hundreds of symbols in real time? That’s genuinely powerful.
# Watchlist column: shows "LONG", "SHORT", or "FLAT"
# Add via: Setup → Customize... → Custom column
def fastEMA = ExpAverage(close, 21);
def slowEMA = ExpAverage(close, 55);
AddLabel(yes,
if fastEMA > slowEMA then "LONG"
else if fastEMA < slowEMA then "SHORT"
else "FLAT",
if fastEMA > slowEMA then Color.GREEN
else if fastEMA < slowEMA then Color.RED
else Color.GRAY
);
Scanner Scripts
The Stock Hacker scanner is, in our honest opinion, the single best reason to use ThinkorSwim. Period. You write a ThinkScript condition, and TOS scans the entire market in real time to find matches. We’ve built scanner scripts that surface trade setups we’d never find manually scrolling through charts.
# Scan: EMA crossover happened TODAY with volume surge
def fastEMA = ExpAverage(close, 21);
def slowEMA = ExpAverage(close, 55);
def crossToday = fastEMA crosses above slowEMA within 1 bars;
def volumeSurge = volume > Average(volume, 20) * 2;
plot scan = crossToday and volumeSurge;
# Scan: Stocks near 52-week high with strong momentum
def nearHigh = close >= Highest(high, 252) * 0.95;
def strongRSI = RSI(14) > 60 and RSI(14) < 80;
def aboveEMA = close > ExpAverage(close, 50);
plot scan = nearHigh and strongRSI and aboveEMA;
Conditional Orders (Semi-Automation)
Let us be blunt. ThinkorSwim does not do automated trading. Not really. If you’re coming from MT4 where your EA fires orders at 3 AM while you sleep — that doesn’t exist here. What TOS gives you instead is conditional orders, which is basically “we’ll place this order IF this study condition becomes true.” It’s a workaround. A clever one, but a workaround nonetheless.
Setting Up Conditional Orders
- Right-click chart → Buy Custom → With Triggers
- In the Order Entry:
- Set: “Submit at” → market condition
- Condition: “Study” → your custom study
- Trigger: study signal == 1
- Set bracket order (SL + TP) automatically
- Enable: “Good ’til cancel” (GTC)
Alert-Based Workflow
The other approach — and honestly the one we use more often — is alerts. Build a study with your entry conditions, attach an alert, and let TOS notify you when something fires. Then you decide whether to pull the trigger manually.
# Custom study that triggers an alert
def buyCondition = fastEMA crosses above slowEMA
and close > ExpAverage(close, 200)
and volume > Average(volume, 20) * 1.5;
Alert(buyCondition, "BUY SIGNAL: EMA Crossover + Volume",
Alert.BAR, Sound.Ding);
plot signal = if buyCondition then 1 else 0;
Backtesting in TOS
Real talk about TOS backtesting. It exists. It’s free. And it’s extremely limited compared to what you get in MetaTrader or even TradingView. No position sizing logic. No portfolio-level testing. No walk-forward optimization. The P&L report gives you surface-level stats without the depth serious algo traders need. That said — for a quick gut-check on whether your logic is completely broken? It does the job. Just don’t confuse a TOS backtest with rigorous strategy validation.
Adding a Strategy to Chart
- Studies → Edit Studies → Strategies tab
- Select your strategy → Add → OK
- Strategy appears with entry/exit markers on chart
Key Report Metrics
Right-click strategy signals on chart → Show Report
- Total P&L
- Number of trades
- Win rate
- Avg winner / Avg loser
- Max drawdown
- Profit factor
Important Limitations
| Limitation | Workaround |
|---|---|
| No position sizing in backtest | Use fixed quantity; calculate size manually |
| No portfolio-level testing | Test each symbol independently |
| No walk-forward optimization | Export data → optimize in Python |
| Limited historical data | Use OnDemand for extended replay |
| No tick-level precision | Use 1-minute bars minimum |
Useful ThinkScript Functions
# Price references
close, open, high, low, volume, hl2, hlc3, ohlc4
# Indicators (built-in)
SimpleMovingAvg(close, 20)
ExpAverage(close, 20)
RSI(14)
MACD(12, 26, 9)
ATR(14)
BollingerBands(20, 2)
Highest(high, 20)
Lowest(low, 20)
# Time functions
GetDay(), GetMonth(), GetYear()
GetDayOfWeek(GetYYYYMMDD())
SecondsFromTime(0930)
SecondsTillTime(1600)
# Cross detection
crosses above, crosses below
crosses above within N bars
# Aggregation (MTF)
close(period = AggregationPeriod.DAY)
close(period = AggregationPeriod.WEEK)
# Bar counting
BarNumber()
if BarNumber() == 1 then ... (first bar logic)
# State tracking with rec (recursive)
rec tradeCount = if buySignal then tradeCount[1] + 1
else tradeCount[1];
ThinkScript Tips
Twelve years of hard-won lessons, distilled. Don’t skip these.
- Use
deffor intermediate calculations — this isn’t optional advice, it’s survival. ThinkScript withoutdefturns into spaghetti fast. Name your intermediate values. Your future self will thank you. recfor state tracking — ThinkScript has no variables in the traditional sense. Everything is a series.recis the hack that lets you carry a value from one bar to the next. Ugly but essential.Double.NaNhides plots — want a plot to appear only when a condition is true? ReturnDouble.NaNotherwise. This is ThinkScript’s version of “don’t draw anything here.” You’ll use it constantly.- Test on OnDemand first — TOS has a market replay feature. Use it. Run your study against replayed data before you trust it with real money.
- Label everything —
AddLabel()andAddChartBubble()are yourPrint()statements. ThinkScript has no console, no debugger, no breakpoints. Labels on the chart are literally all you’ve got. - Watch computation limits — TOS will flat-out refuse to load your study if it’s too complex. If you hit it, you need to simplify — combine conditions, reduce lookback periods, remove redundant calculations.
ThinkScript is quirky, the documentation is a mess, and debugging is basically “stare at chart labels until enlightenment strikes.” If you’d rather skip the pain and get a working study, strategy, or scanner script built right the first time, that’s what we do at Quantumcona. Custom indicators, watchlist columns, conditional order setups — we’ve built hundreds of them.
Article 19 of 20 in the Algo Trading Masterclass by Quantumcona. Next up: we wrap the whole series.

