Code Structure
Fundamental patterns for organizing your kScript code.
Variable Declarations
Use the appropriate variable type for your data.
Static variables for constants:
Use for constants and values that never change across bars (e.g., periods, thresholds, multipliers).
static period = 14
static multiplier = 2.0Regular variables for dynamic values:
Use for dynamic values that change per bar but don't need historical access (e.g., signals, calculations).
var signal = 0
var trend_direction = "up"Timeseries for historical data:
Use only when absolutely necessary as they consume more memory. Declare as timeseries when you need to:
- Store data source outputs:
timeseries price_data = ohlcv(...) - Access past values at previous candle indices:
sma_values[5]
While indicator functions like sma(), ema(), and rsi() require a timeseries as input, their return values should be stored as var unless you specifically need to access historical values (e.g., sma_values[5]). This saves memory and improves performance.
// ✓ Good - Data source stored as timeseries
timeseries price_data = ohlcv(currentSymbol, currentExchange)
// ✓ Good - Indicator return value stored as var (unless you need history)
var sma_value = sma(price_data.close, period)
var rsi_value = rsi(price_data.close, 14)
// ✓ Only use timeseries if you need to access historical values
timeseries sma_series = sma(price_data.close, period)
var previous_sma = sma_series[5] // Accessing 5 bars agoTechnical Indicators
Guidelines for working with technical indicators effectively.
Explicit Parameters
Always specify parameters explicitly for clarity. This makes your code more readable and maintainable.
// ✓ Good - Parameters are explicit and clear
timeseries sma20 = sma(price_data.close, period=20)
timeseries rsi14 = rsi(price_data.close, period=14)
// ❌ Avoid - Relying on defaults makes intent unclear
timeseries sma_default = sma(price_data.close)Historical Data Access
Use bracket notation to access historical values safely.
// Access previous bar values
var prev_close = price_data.close[1]
var close_5_bars_ago = price_data.close[5]
// Use in calculations
var price_change = price_data.close - price_data.close[1]Plotting & Visualization
Create clear and informative visualizations.
Descriptive Plots
Use meaningful colors and widths to make your plots easy to distinguish.
// Use meaningful colors and widths
plotLine(value=sma20, width=2, colors=["blue"], label=["SMA 20"], desc=["20-period Simple Moving Average"])
plotLine(value=sma50, width=2, colors=["red"], label=["SMA 50"], desc=["50-period Simple Moving Average"])
// Conditional plotting with ternary operators
var signal_value = trend_up ? price_data.high : na
plotShape(value=signal_value, shape="circle", width=2, colors=["green"], label=["Signal"], desc=["Trading Signal"])Choose Appropriate Plot Types
Select the right plot function for your data type.
Debugging
Best practices for debugging and inspecting your kScript code.
Use printTimeSeries for Timeseries Objects
Always use printTimeSeries() for timeseries objects instead of print(). The print() function only outputs the first value (e.g., only "open" for OHLCV data), while printTimeSeries() displays the complete timeseries structure.
// ❌ Avoid - print() only shows first value (open)
timeseries price_data = ohlcv(currentSymbol, currentExchange)
print("Price data:", price_data) // Only prints open values
// ✓ Good - printTimeSeries() shows complete OHLCV structure
printTimeSeries(price_data) // Prints [timeseriesInMs, open, high, low, close, volume]
// You can also specify which field to print
printTimeSeries(price_data, priceIndex=4) // Print [timeseriesInMs, close] pricesError Prevention
Protect your code against common runtime errors.
Null Checking
Always check for invalid data before performing calculations.
// Check for invalid data before calculations
var calculation = !isnan(price_data.close) ? price_data.close * multiplier : na
// Alternative using isnum()
if (isnum(price_data.close) && price_data.close > 0) {
var result = price_data.close * 2
}Division by Zero
Protect against division by zero errors.
// Protect against division by zero
var ratio = denominator != 0 ? numerator / denominator : na
// With additional safety checks
var safe_ratio = isnum(denominator) && denominator != 0 ? numerator / denominator : naArray Bounds
Be careful when accessing historical data to avoid index errors.
// Check bar index before accessing historical data
var safe_previous = barIndex > 0 ? price_data.close[1] : price_data.close
// For multiple bars back
var safe_value = barIndex >= 5 ? price_data.close[5] : price_data.closePerformance Optimization
Write efficient code that executes quickly.
Avoid Redundant Calculations
Store frequently used calculations in variables instead of recalculating.
// ✓ Good - Calculate once and reuse
static lookback_period = 20
timeseries typical_price = (price_data.high + price_data.low + price_data.close) / 3
timeseries sma_typical = sma(typical_price, lookback_period)
// ❌ Avoid - Recalculating the same value multiple times
var value1 = sma((price_data.high + price_data.low + price_data.close) / 3, 20)
var value2 = sma((price_data.high + price_data.low + price_data.close) / 3, 20)Use Static Variables
Declare constants as static to avoid unnecessary recalculation on every bar.
// ✓ Good - Declare constants as static
static fibonacci_ratio = 0.618
static golden_ratio = 1.618
static pi = 3.14159
// These values don't change, so no need to recalculate each barCode Organization
Structure your code for maximum readability and maintainability.
Group Related Code
Organize your code into logical sections: inputs, data sources, calculations, signals, and plotting.
// @version=2
define("My Strategy", "overlay", overlay=true)
// Input parameters
var period = input("Period", "number", defaultValue=14)
var threshold = input("Threshold", "number", defaultValue=0.5)
// Data sources
timeseries price_data = ohlcv(currentSymbol, currentExchange)
// Calculations
timeseries ma_fast = sma(price_data.close, period)
timeseries ma_slow = sma(price_data.close, period * 2)
// Signals
var buy_signal = crossover(ma_fast, ma_slow)
var sell_signal = crossunder(ma_fast, ma_slow)
// Plotting
plotLine(value=ma_fast, width=2, colors=["blue"], label=["Fast MA"], desc=["Fast Moving Average"])
plotLine(value=ma_slow, width=2, colors=["red"], label=["Slow MA"], desc=["Slow Moving Average"])
plotShape(value=buy_signal ? price_data.low : na, shape="circle", width=2, colors=["green"], label=["Buy Signal"], desc=["Buy Signal Marker"])Use Comments Effectively
Write clear comments that explain the "why" behind your code logic.
// Calculate RSI divergence
var rsi_current = rsi(price_data.close, 14)
var rsi_previous = rsi_current[1]
// Look for bullish divergence:
// Price makes lower low but RSI makes higher low
var price_lower_low = price_data.low < price_data.low[1]
var rsi_higher_low = rsi_current > rsi_previous
var bullish_divergence = price_lower_low && rsi_higher_lowUse Descriptive Labels and Descriptions
Always include label and desc parameters in your plotting functions. These parameters improve autocomplete functionality, make scripts more readable, and help with script organization in the editor.
// ✓ Always use label and desc parameters for better organization
timeseries price_data = ohlcv(currentSymbol, currentExchange)
var sma20 = sma(price_data.close, 20)
var sma50 = sma(price_data.close, 50)
var rsi = rsi(price_data.close, 14)
// Good: Descriptive labels help with autocomplete and script organization
plotLine(value=sma20, width=2, colors=["blue"], label=["SMA 20"], desc=["20-period Simple Moving Average"])
plotLine(value=sma50, width=2, colors=["red"], label=["SMA 50"], desc=["50-period Simple Moving Average"])
plotLine(value=rsi, width=2, colors=["purple"], label=["RSI"], desc=["14-period Relative Strength Index"])
// ❌ Avoid: Missing labels make scripts harder to understand
plotLine(value=sma20, width=2, colors=["blue"])
plotLine(value=sma50, width=2, colors=["red"])
plotLine(value=rsi, width=2, colors=["purple"])