Skip to content

Best Practices & Common Mistakes

Tips for writing reliable, performant CodeLab indicators.

Handle the First Bar

Many calculations need a warmup period. On bar 0, there's no previous data.

typescript
calculate() {
  // Guard: need at least `period` bars for a meaningful value
  if (this.bar < this.length) return;

  // Or use isFirstBar for initialization
  if (this.isFirstBar) {
    this.setState('prevSignal', 0);
  }
}

TIP

Built-in functions (sma, ema, rsi, etc.) handle warmup internally. You only need guards for your own custom calculations.

Don't Use setState for Built-in Functions

Built-in functions manage their own state. Wrapping them in setState is redundant and can cause bugs.

typescript
// BAD - don't do this
this.setState('myEma', this.ema('close', 14));
const prev = this.getState('myEma');

// GOOD - the function tracks its own state
const emaVal = this.ema('close', 14);
plot('EMA', emaVal);
const prevEma = this.getPlot('EMA', 1); // previous bar's value

Cross Detection Requires 4 Arguments

crossOver and crossUnder need both current AND previous values.

typescript
// BAD - only 2 args, won't work
if (this.crossOver(fast, slow)) { ... }

// GOOD - 4 args with previous bar values via getPlot
const fast = this.ema('close', 9);
const slow = this.ema('close', 21);
plot('Fast', fast);
plot('Slow', slow);

const prevFast = this.getPlot('Fast', 1);
const prevSlow = this.getPlot('Slow', 1);
if (this.crossOver(fast, slow, prevFast, prevSlow)) { ... }

Keep Built-in Functions Outside Conditionals

Built-in functions that manage state (ema, rsi, macd, etc.) must be called every bar so their internal state stays consistent. Don't put them inside if/else blocks.

typescript
// BAD - ema is only called some bars, state gets out of sync
if (someCondition) {
  const ema = this.ema('close', 14);
  plot('EMA', ema);
}

// GOOD - always call, conditionally use
const ema = this.ema('close', 14);
if (someCondition) {
  plot('EMA', ema);
}

Plot Names Must Be Unique

Each plot(), plotHistogram(), and plotColor() call needs a unique name.

typescript
// BAD - duplicate name
plot('Value', smaValue);
plot('Value', emaValue);  // Overwrites the first!

// GOOD - unique names
plot('SMA', smaValue);
plot('EMA', emaValue);

Class Names Must Be PascalCase Alphanumeric

typescript
// BAD
export default class my-indicator extends Script { ... }
export default class My Indicator extends Script { ... }

// GOOD
export default class MyIndicator extends Script { ... }
export default class BollingerBands extends Script { ... }
export default class RSIMomentum extends Script { ... }

Use Overlay Correctly

Indicators that should be at price scale (moving averages, bands, channels) need overlay: true. Oscillators (RSI, MACD, Stochastic) should NOT use overlay.

typescript
// Moving average - OVERLAY (same scale as price)
plot('SMA', smaVal, { color: color.blue, overlay: true });

// RSI - SUB-PANE (0-100 scale, not price scale)
plot('RSI', rsiVal, { color: color.purple });

NaN / Undefined Safety

If you access data before it exists (e.g., this.get('close', 100) when only 50 bars are loaded), the value may be NaN. The SDK sanitizes plot values, but your intermediate calculations should handle this:

typescript
const prevClose = this.bar > 0 ? this.get('close', 1) : this.close;
const change = this.close - prevClose;  // Safe on bar 0

Performance Tips

  • Avoid expensive loops in calculate() — it runs once per bar, so keep it lean
  • Use built-in functions (sma, highest, etc.) instead of manually looping over historical data
  • Don't log on every barthis.log() only outputs on the last bar by design
  • Limit state keys — each setState() creates a data row. Use meaningful, consistent key names

Import Only What You Use

typescript
// BAD - importing everything
import { Script, Strategy, input, plot, plotHistogram, plotColor, hline, plotShape, plotArrow, plotArea, bgcolor, barcolor, color } from '@chartlabs/script-sdk';

// GOOD - import only what you need
import { Script, input, plot, plotColor, hline, color } from '@chartlabs/script-sdk';

Debugging Tips

  1. Use this.log() to print values on the last bar
  2. Start simple — get basic logic working before adding complexity
  3. Test on multiple instruments — some instruments may have different data characteristics
  4. Check bar count — short charts may not have enough bars for long periods

ChartLabs Documentation