TessTrade Python SDK
Write indicators and strategies in Python. The same script runs in two contexts - a historical backtest and live chart trading - under an identical contract. This documentation covers how the engine calls your code, what the SDK exposes, the set of accepted actions, and the most common pitfalls.
INFO
New here? Start with Backtest vs Chart Trading for the mental model, then jump to the Example script to write your first strategy in five minutes.
What you write
A Python file with a main(df, sdk, params) function and, optionally, a DECLARATION dictionary describing the editable parameters and the chart plots.
DECLARATION = {
"type": "strategy",
"inputs": [
{"name": "fast_period", "type": "int", "default": 9, "min": 1, "max": 100},
{"name": "slow_period", "type": "int", "default": 21, "min": 2, "max": 200},
],
}
def main(df=None, sdk=None, params={}):
params = params or {}
if sdk is not None:
# tick by tick / candle by candle execution
return on_bar_strategy(sdk, params)
if df is not None:
# chart plots
return _build_chart(df, params)
return DECLARATIONThe same function is called in three different contexts. See The main() dispatcher for details on each one.
Documentation map
Getting started
| Page | Description |
|---|---|
| Backtest vs Chart Trading | The two execution contexts and why the same script runs in both |
| Example script | A minimal runnable strategy to verify the editor and the pipeline |
| Sandbox limits | Available modules, builtins, and resource caps |
Contract
| Page | Description |
|---|---|
The main() dispatcher | The three contexts the engine calls your script in |
The DECLARATION shape | Root-level dictionary for inputs, plots, and conditions |
| Script lifecycle | How the engine loads, validates, and executes your code |
SDK reference
| Page | Description |
|---|---|
| Candles, params and state | Reading market data and persisting values across bars |
| Position, cash and equity | Portfolio properties exposed on the SDK |
| Canonical actions | Every action string the engine accepts |
| Order types | Market, limit, stop, and attached stop/target |
| Stops, targets and trailing | Protective stops, take-profit, and trailing stops |
Indicators
| Page | Description |
|---|---|
| Anatomy of a custom indicator | The canonical file structure — parameters and colors first, then math |
Plots and series | How chart plots are declared and rendered |
| Implementing SMA and EMA | Moving average recipes in pure Python |
| RSI, MACD and Bollinger Bands | Pure-Python implementations of the three most common oscillators |
| Panes: overlay vs new pane | When to overlay on price versus use a separate pane |
| Native indicator kernels | The shared kernels the chart renders with — reach them from your script through the injected Indicator global (no import) and through pandas_ta |
Ready-to-use strategies
| Page | Description |
|---|---|
| SMA Crossover | Dual moving-average trend-following template |
| RSI Mean Reversion | Overbought/oversold reversal template |
| MACD Momentum | Signal-line crossover template |
| Persistent state and trailing stop | Patterns for sdk.state and trailing exits |
| Solid entry/exit patterns | Checklist of robust practices |
Declarative mode
| Page | Description |
|---|---|
| When to use entry/exit conditions | Declarative versus imperative mode and how to choose |
| Supported operators | Operator catalog with semantics |
Backtest
| Page | Description |
|---|---|
| Reading the results | Interpreting the output panel |
| Performance metrics | Sharpe, drawdown, profit factor, and which ones actually matter |
| Troubleshooting | Common failure modes with root cause and fix |
Chart Trading
| Page | Description |
|---|---|
| Live editor | In-chart development environment |
| Paper Trading Bots | Persistent simulated bots that run without an open tab |
| Live vs backtest differences | Subtle execution gaps between the two contexts |
Reference
| Page | Description |
|---|---|
| Canonical actions table | Quick lookup for every accepted action |
| Operators table | Quick lookup for declarative operators |
| Error catalog | Every exception the engine can raise and how to fix it |
| Glossary | Term definitions |
What the SDK provides
sdk.candles- list of OHLCV candles.sdk.params- the parameters defined inDECLARATION["inputs"], already typed.sdk.state- dictionary persistent across candles (useful for trailing stops, flags, cooldowns).sdk.position- current net position (positive = long, negative = short, zero = flat).sdk.cash,sdk.equity,sdk.buy_price,sdk.sell_price- portfolio snapshot.sdk.buy(...),sdk.sell(...),sdk.close(...),sdk.update_exits(...)- issue signals.
Full API in SDK reference.
Sandbox
Scripts run in a hardened Python sandbox. Available resources:
- Injected modules (no
importrequired):np(numpy),pd(pandas),math,json,datetime, plus safe optimized versions ofta,pandas_ta, andtalib. - Injected indicator globals (no
importrequired):IndicatorandSignal.Indicator.sma/ema/rsi/macd/bollingerexpose the same kernels the live chart renders with (so your math matches the chart exactly), computed over the candle window you pass in — see Native indicator kernels. Thetesstrade_indicatorsmodule itself is an internal native (PyO3, chart-side) detail and is not importable from the editor. - Allowlisted imports (opt-in
import):numpy,pandas,pandas_ta,talib,math,json,datetime. Nothing else may be imported. - Allowed builtins:
len,range,sum,abs,min,max,round,sorted,zip,enumerate,isinstance,int,float,str,list,dict,tuple,set,print. - Exception classes:
ValueError,TypeError,KeyError,IndexError,RuntimeError,ZeroDivisionError,OverflowError.
Anything outside this surface raises SecurityError. Full detail in Sandbox limits.
Execution limits
| Limit | Default | Raised if exceeded |
|---|---|---|
| Time per bar | 800ms | TimeoutError (isolated bars are tolerated; the run only aborts when recoverable failures cross both 5% of bars and at least 5 absolute failures) |
| Memory | per-strategy ceiling | MemoryError |
| Code size | ~100KB | rejected at load time |
| Nesting depth | 50 levels | rejected at load time |
| Code complexity | ~10000 syntax nodes | rejected at load time |
⚠️ A persistently slow frame is not just a tolerated
TimeoutError. When a bar overruns the per-bar budget, the worker's reader abandons the late response and the request/response protocol can desynchronize — the next read then consumes an out-of-order line and the backtest dies withProtocolError: Failed to parse persistent strategy output JSON: data did not match any variant of untagged enum StrategyOutput. Unlike aTimeoutError, thisProtocolErroris fatal and is not covered by the 5% tolerance — it aborts the whole run immediately. The only reliable cure is to keep every frame O(1) per bar (incremental indicators), not to rely on the tolerance.
TIP
Quick start: copy the SMA Crossover template, adjust the parameters, and run it. From there, swap in the indicator of your choice from Implementing SMA and EMA or RSI, MACD and Bollinger Bands.