Appearance

Personalize your experience

Mode
Accent Color
Direction
Script Best Practices

Best Practices

IDE

Follow these guidelines to write performant, reliable, and maintainable trading scripts.

1.1 Avoid Redundant Computation

The () function is called for every bar on the chart. Avoid expensive operations inside the loop:

typescript
// ❌ Bad: Recalculates entire SMA from scratch for every barfunction calculate(bars: BarData[], params: any) {  return bars.map((bar, i) => {    // O(n) per bar → O(n²) total    const slice = bars.slice(Math.max(0, i - params.period + 1), i + 1);    const sum = slice.reduce((s, b) => s + b.close, 0);    return { time: bar.time, value: sum / slice.length };  });}// ✅ Good: Running sum — O(1) per bar → O(n) totalfunction calculate(bars: BarData[], params: any) {  const period = params.period;  const result: IndicatorValue[] = [];  let sum = 0;  for (let i = 0; i < bars.length; i++) {    sum += bars[i].close;    if (i >= period) sum -= bars[i - period].close;    if (i >= period - 1) {      result.push({ time: bars[i].time, value: sum / period });    }  }  return result;}

1.2 Minimize Canvas Operations

typescript
// ❌ Bad: Multiple save/restore per elementfunction render(ctx, values) {  values.forEach(v => {    ctx.save();              // Expensive per iteration    ctx.strokeStyle = v.color;    ctx.beginPath();    ctx.moveTo(v.x, v.y);    ctx.lineTo(v.x2, v.y2);    ctx.stroke();    ctx.restore();           // Expensive per iteration  });}// ✅ Good: Batch by color, single save/restorefunction render(ctx, values) {  ctx.save();    // Group by color  const groups = new Map<string, typeof values>();  values.forEach(v => {    if (!groups.has(v.color)) groups.set(v.color, []);    groups.get(v.color)!.push(v);  });    // Draw each color group in one path  groups.forEach((items, color) => {    ctx.strokeStyle = color;    ctx.beginPath();    items.forEach(v => {      ctx.moveTo(v.x, v.y);      ctx.lineTo(v.x2, v.y2);    });    ctx.stroke();  // Single stroke call per color  });    ctx.restore();}

1.3 Python Strategy Performance

python
# ❌ Bad: Row-by-row iteration in Pythonfor i in range(len(df)):    if df.iloc[i]['close'] > df.iloc[i]['sma_20']:        signals.append('BUY')# ✅ Good: Vectorized with pandas/numpysignals = np.where(df['close'] > df['sma_20'], 'BUY', 'HOLD')# ❌ Bad: Recomputing entire rolling windowdf['sma'] = df['close'].apply(lambda x: df['close'].rolling(20).mean())# ✅ Good: Single vectorized operationdf['sma'] = df['close'].rolling(20).mean()

2.1 Handle Edge Cases

typescript
// Always handle insufficient datafunction calculate(bars: BarData[], params: any) {  const period = params.period;  if (bars.length < period) return [];  // Not enough data  // Handle NaN values  const output = computeIndicator(bars, period);  return output.filter(v => !isNaN(v.value) && isFinite(v.value));}// Handle division by zerofunction rsi(gains: number, losses: number): number {  if (losses === 0) return 100;   // No losses = max RSI  if (gains === 0) return 0;     // No gains = min RSI  return 100 - (100 / (1 + gains / losses));}

2.2 Validate Configuration

typescript
// Validate params before usefunction calculate(bars: BarData[], params: any) {  const period = Math.max(1, Math.floor(params.period || 14));  const multiplier = Math.max(0.1, params.multiplier || 2.0);  const source = params.source || 'close';    // Validate source  const validSources = ['close', 'open', 'high', 'low', 'hl2', 'hlc3', 'ohlc4'];  if (!validSources.includes(source)) {    throw new Error(`Invalid source: ${source}. Use one of: ${validSources.join(', ')}`);  }}

2.3 Python Error Handling

python
def generate_signal(bars, position, config):    try:        df = pd.DataFrame(bars)        if len(df) < config.get('min_bars', 50):            return {'signal': Signal.HOLD, 'reason': 'Insufficient data'}                # Your strategy logic        signal = compute_signal(df, config)        return signal            except Exception as e:        # Never let uncaught exceptions crash the strategy        return {            'signal': Signal.HOLD,            'reason': f'Error: {str(e)}',            'error': True,        }

3.1 Structure Your Script

typescript
// ✅ Good structure for an indicator script:// 1. Metadata (required)export const metadata = { ... };// 2. Default configuration (required)export const defaultConfig = { ... };// 3. Helper functions (internal, not exported)function computeSMA(values: number[], period: number): number[] { ... }function computeEMA(values: number[], period: number): number[] { ... }// 4. Main calculation (required export)export function calculate(bars: BarData[], params: any) { ... }// 5. Custom render (optional export)export function render(ctx: CanvasRenderingContext2D, ...) { ... }

3.2 Naming Conventions

ItemConventionExample
Script IDkebab-casemy-custom-
Script NameTitle CaseMy Custom
ParameterscamelCasesmoothingPeriod, priceSource
FunctionscamelCasecalculateEMA(), renderHistogram()
ConstantsUPPER_SNAKE_CASEMAX_PERIOD, DEFAULT_COLOR
Python snake_casefast_period, signal_strength
Python functionssnake_casegenerate_signal(), calculate_rsi()

4. Testing Your Scripts

Always verify these before saving:

  • Script loads without errors on the chart
  • Output values are reasonable (no NaN, no extreme outliers)
  • Works with different timeframes (1m, 5m, 1H, 1D)
  • Works with different symbols (stocks, crypto, forex)
  • Handles chart resize and zoom correctly (overlays)
  • Handles gaps in (weekends, holidays)
  • and take profit correctly (strategies)
  • Performance is acceptable with 5000+ bars

5. Common Mistakes

MistakeProblemFix
Using in calculationsOnly reference bars[0..i], never bars[i+1..]
Survivorship biasOnly testing on winning symbolsTest on a diverse set of instruments
OverfittingToo many parameters tuned to historyUse out-of-sample testing, keep params simple
No Single trade can wipe out capitalAlways define a in
Position sizing ignored lot sizes regardless of riskUse -based tied to distance
Ignoring slippageBacktest looks better than realitySet slippage_pct ≥ 0.05% for realistic
Memory leaksGrowing arrays in ()Always return -size arrays, don't accumulate state

Related articles

  • Evaluation Performance

    Coding / Performance

    Why a backtest is slow and what actually fixes it: condition ordering, indicator reuse, incremental computation, and the difference between an O(n) and an…

  • Condition Evaluator

    Automation / Strategy Builder / Reference

    How a condition becomes true on a bar: series computed up front, no value during warm-up, the exact cross and rising rules, and the order of work on each…

  • Writing Overlays in the IDE

    Coding / IDE / Overlays

    Overlays are visual elements drawn on top of the price chart. They include annotations, zones (supply/demand, support/resistance), drawing tools, and custom…

  • Building Custom Indicators

    Coding / Custom Indicators

    Composing new indicators from existing ones, the recursive shapes that need care, warm-up handling, and how to check a custom indicator is actually correct.

  • Coding for Trading — Fundamentals

    Coding / Fundamentals

    The concepts every strategy rests on, whether you write it in the condition builder or in code: series, bar indexing, the difference between a value and a…

  • Strategy DSL — Execution Model

    Coding / Strategy DSL

    What the engine does on every bar: protective exits, exit signals, entries, layers and re-entries, sizing and costs, and where every statistic in the result…

  • Writing DSL by Hand

    Coding / Strategy DSL

    Authoring strategy JSON directly: the minimum document, small constructors that keep it readable, validating before anything runs, generating variants, and…

  • Built-in Indicators & Overlays

    Coding / IDE / Reference

    Quatick ships with a full library of pre-built indicators and overlays. You can use these out of the box, or study their implementations as reference for…

  • Engines — Python vs C++

    Automation / Predefined Strategies

    Two independent implementations of the same grid semantics, consuming the same config and the same command channel. The choice is a runtime choice, not a…

Browse all knowledge base articles →