Conditions and indicators¶
A condition is a true-or-false test. Combine price values and indicator calculations to describe when a position should open or close.
State versus crossing¶
ema(20) > ema(50) is true on every eligible bar where the shorter EMA is above the longer one. ema(20) crosses over ema(50) is true only on the crossing bar.
//@version=1
symbol: TCS
run: daily
size: 5% per instrument
entry:
long when: ema(20) crosses over ema(50)
exit:
long when: ema(20) crosses under ema(50)
Choose a crossing for a transition, or a state comparison for the relationship itself. They can produce different entry opportunities.
A cross over requires the first value to be above the second now, and at or below it on the previous bar. A cross under reverses those comparisons. If an entry is blocked on the crossing bar, the cross is not automatically saved for a later entry. A state comparison may still be true on a later eligible bar.
Read indicator arguments and thresholds¶
| Expression | What the numbers mean | What it compares |
|---|---|---|
rsi(14) > 50 |
14 is the RSI period; 50 is the threshold. |
RSI on its 0–100 scale. |
close > ema(20) |
20 is the EMA period. |
Two prices in the instrument's price units. |
ema(20) > ema(50) |
Both numbers are periods. | A shorter and a longer moving average. |
close > close[1] |
1 is a history offset. |
Current close versus the previous close. |
Periods count bars at the signal interval. A 20-period indicator on 15-minute data has a different horizon from one on daily data. RSI thresholds are not percentages of your capital. Open the indicator library to choose a function and read its arguments, defaults, units and examples. Start with moving averages, RSI and MACD, or volatility and channels. Functions are available directly in TradeScript; no imports are needed.
Conditions need valid history for every value they read, including previous values used by a crossing. Do not interpret missing warmup values as a trading signal.
Provider availability¶
A function name must refer to a supported calculation or a custom provider supplied by your execution environment. An unknown-provider advisory permits custom integrations; it does not install a function or prove it is available. Confirm its arguments, units, history needs and instrument scope before using it.
For example, suppose your environment supplies custom_signal(10) as a
per-instrument score from 0 to 1 over ten bars:
//@version=1
symbol: TCS
run: each bar
size: 1 share per instrument
entry:
long when: custom_signal(10) > 0.7
exit:
long when: custom_signal(10) < 0.3
A score of 0.8 requests a one-share long target. A score of 0.2 requests closing an existing long holding; it does not open a short. These thresholds are score values, not capital percentages. The ten-bar window is this example provider's contract, not a default imposed on all custom functions. Normal entry state and fill timing still apply.
Without that provider, a real-provider run reports an unresolved name when the expression is evaluated. Ask the environment owner to supply the provider before running; changing the threshold cannot fix a missing function. Demo stubs remain the default for standalone lowering, but their fabricated values do not verify the score or establish real-provider availability.
Only abs, min, max, len, sum and round have intentional builtin
fallback. Other Python builtin names, including quit, exit, help, print
and type, are reserved rather than custom providers, in calls, arguments and
definitions alike. Rename a custom provider that uses a reserved name and update
its environment binding and strategy together. open remains a price series,
as in close > open; open(...) is not a supported function. These checks do
not change //@version=1 or the normal indicator shorthand.
Custom providers use plain names such as custom_signal(10), not object members
such as library.custom_signal. Member access is limited to the documented
fields of a declared session_range(...) binding: for example, after
define orb = session_range("09:15", "09:30"), close > orb.high compares the
current close with that range's high. Arbitrary members, nested member chains,
and reserved bases such as __builtins__ are rejected during both validation
and lowering, including inside block definitions. Assigning a member to a local
name does not make it a supported provider. Ask the environment owner to expose
the calculation under a plain, non-reserved name instead. This restores the
existing version-1 member rules; previously, skipping validation could let an
invalid expression reach execution. Injected providers themselves remain trusted
code supplied by the environment, not sandboxed by these expression rules.
Combine tests¶
| Expression | Meaning |
|---|---|
rsi(14) > 50 and close > ema(20) |
Both tests must be true. |
rsi(14) < 30 or close < ema(50) |
At least one test must be true. |
not holding |
There is no open position. |
(rsi(14) < 30 or close < ema(50)) and flat |
Group alternatives, then require a flat position. |
not binds before and, and and before or. Use parentheses for grouping. Write rsi(14) > 30 and rsi(14) < 70; chained comparisons such as 30 < rsi(14) < 70 are unsupported.
> and < exclude equality; >= and <= include it. Use == to test equality and != for inequality. A single = belongs in definitions, not comparisons.
Position state and direction¶
flat means there is no position; holding means a position is open, whether long or short. not holding therefore means flat, not short. With a universe, these predicates refer to the instrument whose rule is being checked.
By default, an entry condition requests its named direction and waits while that direction is already held. An exit such as long when: applies to a long holding; short when: applies to a short holding. Use a plain when: inside exit: for a condition that should close either side. See long and short rules before deriving one direction from the other.
Enter again while holding¶
Add even if holding to an entry when each new purchase should have its own lifetime:
//@version=1
symbol: TCS
run: 5min, Mon-Fri, 09:15-15:30
size: 10 shares per instrument
entry:
long at 09:15 even if holding
exit:
long after 1 session at 11:30
Monday's entry requests 10 shares. Tuesday at 09:15 requests another 10 while Monday's purchase remains open. Tuesday at 11:30 requests the sale of Monday's remaining shares. Wednesday at 11:30 sells Tuesday's remainder. Each purchase keeps its own entry session; a later purchase never restarts an earlier timer. These are order request times; fills follow the execution environment, including next-bar fills in replay.
Without the suffix, the existing entry behaviour stays the same. Put the suffix only on entries that may add another purchase. The same form works with short and with each instrument in a fixed universe:.
For a condition, write long when: close crosses over 100 even if holding. The condition still decides when: a crossing gives another opportunity on another crossing, while close > 100 can request another purchase on every eligible bar that remains true. The suffix does not turn a continuous condition into a daily signal. A clock entry makes at most one attempt for that rule and instrument on that session date, including after restart. A missed clock, rejection or insufficient capacity does not create a later catch-up entry.
To buy at a daily clock only when a signal is true, combine the clock and filter:
//@version=1
symbol: TCS
run: 5min, Mon-Fri, 09:15-15:30
size: 10 shares per instrument
entry:
long when: at("09:15") and sma(20) > sma(50) even if holding
exit:
long after 1 session at 11:30
At 09:15, a true SMA condition permits another purchase; a false condition skips
that day's entry. Later SMA changes do not create a catch-up entry or cancel an
earlier purchase's exit. This exact clock combined with and remains one daily
entry opportunity, including repeated callbacks and restarts. Keep enough price
history for both averages. Each average uses the strategy's bar timeframe.
The size describes each additional purchase. For example, size: 5% of portfolio per instrument requests another 5% allocation at each entry, using current capacity. Existing purchases and pending orders still consume capacity; an unfilled sale releases no funds. Units, Forex lots and stop-risk sizing are also supported. Shared budgets, hold:, pairs/options, mixed long/short entry directions, opposite, pyramiding and volatility resizing cannot be combined with this suffix; validation explains the unsupported combination.
An ordinary condition exit, such as long when: close < ema(20), closes all long purchases for that instrument and prevents a contradictory entry on that evaluation. Timed exits and protection apply to their own purchase. Bar-count exits retain the existing continuous time-stop behavior: the first
confirmed fill observation counts toward the bar limit, and later feed bars
continue the count between scheduled signal evaluations. Session counting retains the existing session and history rules: the entry session is zero and only eligible observed session dates count. Keep sufficient history and use a schedule that can evaluate the requested entry and exit clocks.
Live use requires a consumer that supports separate purchases and durable recovery. The initial live integrations are Zerodha, Alpaca, OANDA/paper OANDA and Invsto Paper with confirmed-fill support. Other brokerages reject this option. Unknown fill or publication evidence pauses execution until reconciled; a restart must restore the original run's state, rather than start a fresh ledger against its open inventory.
Read previous bars¶
close is the current close value; close[1] is the previous bar and close[2] is two bars ago. Negative offsets read future data and are rejected.
Split a long rule across lines¶
//@version=1
symbol: TCS
run: daily
size: 5% per instrument
entry:
long when: (
rsi(14) > 50
and ema(20) > ema(50)
and close > ema(20)
)
exit:
long when: close < ema(50)
Line breaks improve reading without changing meaning. Join comparisons with an explicit and or or.
Avoid a self-including breakout test¶
highest(high, 20) includes the current bar's high. Since current close cannot exceed its own high on ordinary OHLC data, close > highest(high, 20) cannot describe a breakout above earlier highs as written.
Decide whether your level includes the current observation. For a comparison with the immediately previous high, close > high[1] reads a known earlier value. For a longer prior-only window, close > donchian_upper(20)[1] compares with the 20-bar upper channel ending one bar ago. See Donchian channels for history and window details.
Keep expression units explicit¶
close > ema(20) * 1.02 compares current price to a level 2% above the EMA. rsi(14) > 50 compares an oscillator to a reading of 50. 2 ATR in a protection declaration describes a volatility-based price distance, not a multiplication expression you can paste into any condition.
Use function arguments in the documented order. TradeScript expressions do not accept Python keyword-argument calls or arbitrary object attributes. Named session-range fields are a specific supported attribute form; reusable definitions do not enable unrestricted Python code.
Next topic Instruments and schedules →