Core Concepts

Type System

A comprehensive guide to kScript's hybrid type system, covering type inference, variable declarations, and data types.

Advanced 15 min read

Introduction

kScript's type system is specifically designed for financial data analysis, featuring a hybrid approach that combines static analysis at compile time with dynamic execution at runtime. Unlike general-purpose programming languages, kScript optimizes for time-series data processing, which is reflected in its type architecture and constraints.

The compiler performs type checking with limited implicit conversions, helping catch type mismatches early during compilation rather than encountering them at runtime.

At its core, kScript distinguishes between two fundamental categories of values: primitive types (numbers, strings, booleans) and time-series types. The latter category represents kScript's domain-specific strength — the timeseries type is the central data structure for handling historical market data, indicators, and any values that change across chart bars.

This page covers everything from basic data types to advanced type conversion rules, data access patterns, and the extensive data sources available in kScript v2. By understanding the type system deeply, you'll be able to write more efficient, error-free trading algorithms and leverage kScript's full analytical capabilities.

Variable Declarations and Type Inference

In kScript, variable declarations use two keywords: var and timeseries. The choice between them is not merely stylistic — it fundamentally determines how the variable behaves throughout your script's execution.

The var Keyword

Variables declared with var hold scalar values: numbers, strings, booleans, or arrays of these types. These values can change from bar to bar as your script executes, but they represent single values at any given point in time. When you access a var variable, you're getting its current value for the current bar being processed.

The type of a var variable is inferred from its initialization expression. If you assign 100, the type is number. If you assign "BTCUSDT", the type is string. Once inferred, the type remains consistent — you cannot assign a string to a variable that was initialized with a number.

var declarations
//@version=2

// Primitive type declarations using var
var price = 45000.5; // Type inferred as: number
var symbol = 'BTCUSDT'; // Type inferred as: string
var isActive = true; // Type inferred as: boolean

// Array type declarations
var periods = [10, 20, 50]; // Type inferred as: number[]
var colors = ['red', 'blue']; // Type inferred as: string[]

// These types are fixed - you cannot later assign a string to 'price'
// price = "invalid";  // ❌ Compilation error: type mismatch

In this example, price is inferred to be of type number, symbol is type string, and isActive is type boolean. The periods variable has type number[] (array of numbers), and the array must be homogeneous — all elements must be numbers.

The timeseries Keyword

The timeseries keyword declares variables that hold historical data — values that exist across multiple bars with temporal alignment. This is kScript's most distinctive feature and the foundation of all technical analysis. A timeseries variable doesn't just hold the current value; it maintains a complete history that can be indexed to access past values.

Timeseries variables are immutable in the sense that you cannot reassign them once declared. This constraint exists because timeseries data represents a continuous stream of market information, and reassigning it would break the temporal continuity. However, you can create new timeseries through operations on existing ones.

timeseries declarations
//@version=2

// TimeSeries declaration - must use 'timeseries' keyword
timeseries ohlcvData = ohlcv(symbol=currentSymbol, exchange=currentExchange);

// Extract individual timeseries fields
timeseries closes = ohlcvData.close;
timeseries volumes = ohlcvData.volume;

// Create new timeseries through operations
timeseries adjusted = closes + 100;  // New timeseries, not just a number

// ❌ Common error: using 'var' instead of 'timeseries'
// var prices = ohlcv(currentSymbol, currentExchange);
// Error: "cannot assign timeseries into 'var' variable"

Here, ohlcvData holds a complete time series of OHLCV (Open, High, Low, Close, Volume) data. When we extract closes by accessing ohlcvData.close, we get another timeseries containing only closing prices. The adjusted variable demonstrates creating a new timeseries through arithmetic — adding 100 to every close price produces a new timeseries, not just a single number.

Primitive Data Types

Primitive types are the fundamental building blocks of kScript expressions. They represent single, indivisible values rather than collections or complex structures. kScript supports five primitive types: number, string, boolean, null, and the special na value.

number

The number type represents all numeric values in kScript, including both integers and floating-point numbers. Unlike many languages that distinguish between int and float, kScript uses a unified numeric type. You can write integer literals like 42 or 1000, and floating-point literals like 3.14159 or 0.001.

Numbers are used extensively throughout trading algorithms: for prices, volumes, periods for technical indicators, thresholds for signals, and mathematical calculations. All standard arithmetic operators (+, -, *, /, %) work with numbers and return numeric results.

Many built-in functions expect number parameters. For instance, sma(source, period) requires period to be a number indicating how many bars to average. When you use technical indicators, they typically return numeric values representing the indicator's calculation at the current bar.

string

Strings represent textual data and are written using double quotes: "BTCUSDT", "binance". They're essential for identifying trading pairs, exchanges, and providing labels for plots and indicators. Strings in kScript are immutable — once created, their content cannot be modified.

String literals can contain special characters and must be properly quoted. kScript uses strings primarily for configuration and identification purposes rather than text processing. You'll use strings when calling data source functions like ohlcv(symbol=currentSymbol, exchange="BINANCE"), where both the symbol and exchange are string values.

boolean

Boolean values are either true or false and are fundamental to conditional logic in trading algorithms. Comparison operators (<, >, ==, !=) return boolean values, as do logical operators (&&, ||, !).

Booleans are crucial for implementing trading signals and conditions. For example, a crossover detection might use:

lines wrap
var crossedAbove = fastMA > slowMA && fastMA[1] <= slowMA[1];

This boolean expression checks if the fast moving average just crossed above the slow one by comparing current and previous values.

na — Not Available

The na value is a special constant in kScript representing "not available" or missing data. It's distinct from null and serves a specific purpose in time-series analysis: when data is unavailable for a particular bar or when a condition isn't met, na is used instead of a numeric or string value.

One powerful use of na is in conditional plotting. When you pass na to a plot function, that data point is skipped, creating gaps in the visualization. This allows you to create indicators that only display under certain conditions without plotting misleading zero values.

Conditional plotting with na
//@version=2

define("Conditional Plot with NA", "onchart", true);

timeseries trade = ohlcv(symbol=currentSymbol, exchange=currentExchange);

var volumeThreshold = 1000;
var maPeriod = 20;

// Calculate moving average
var ma = sma(trade.close, maPeriod);

// Only plot when volume exceeds threshold, otherwise use 'na' to create gaps
var displayValue = (trade.volume > volumeThreshold ? ma : na);

plotLine(value=displayValue, width=2, colors=["#2962FF"], label=["Display Value"], desc=["Display Value"]);

In this example, the moving average is only plotted when volume exceeds a threshold. When volume is low, na is plotted instead, creating gaps in the line. This technique is far superior to plotting zero or leaving the calculation running invisibly — it clearly shows when the condition is active.

TimeSeries: kScript's Core Type

If kScript has a single defining feature, it's the TimeSeries type. This type represents time-aligned data that evolves as your script processes each bar of market history. Understanding timeseries is essential to writing effective kScript indicators and strategies.

What is a TimeSeries?

A timeseries is not just a single value — it's a complete history of values aligned to chart bars. When your script accesses a timeseries variable, it sees the value for the current bar being processed. But crucially, it can also look backward in time using the history reference operator [].

Conceptually, you can think of a timeseries as a two-dimensional array where each row represents a bar (timestamped) and each column represents a data field. For OHLCV data, you have six fields: timestamp, open, high, low, close, and volume. Each field is itself a timeseries, which is why ohlcvData.close returns a timeseries of closing prices.

The key insight is that timeseries variables automatically track history. You don't need to manually store arrays of past values or manage ring buffers. kScript's execution model handles this for you, making historical references both simple and efficient.

Historical Indexing

To access previous values in a timeseries, use square bracket notation with an integer offset. The index [0] (or no index) refers to the current bar, [1] refers to the previous bar, [2] to two bars ago, and so on. This indexing is relative to the current bar being processed.

Historical indexing
//@version=2

define("Historical Access Demo", "offchart", true);

timeseries trade = ohlcv(symbol=currentSymbol, exchange=currentExchange);
timeseries close = trade.close;

// Access current and historical values
var currentClose = close;      // or close[0] - current bar
var previousClose = close[1];  // 1 bar ago
var close5BarsAgo = close[5];  // 5 bars ago

// Calculate change over 5 bars
var change5 = currentClose - close5BarsAgo;

// Detect three consecutive rising bars
var threeUp = (close > close[1]) && (close[1] > close[2]) && (close[2] > close[3]);

plotLine(value=change5, width=2, colors=["green", "red"], label=["Change 5"], desc=["5-Bar Price Change"]);

In this example, we calculate the difference between the current close and the close from 5 bars ago, then detect if the price has risen for three consecutive bars. Note how the comparison close > close[1] creates a boolean expression that's evaluated at each bar — this is the power of series-aware operations.

Immutability and Global Scope

Timeseries variables have two important constraints. First, they are immutable in the sense that you cannot reassign a timeseries variable once declared. You cannot write ohlcvData = someOtherData after the initial declaration. This prevents breaking the temporal continuity of the data.

Second, timeseries variables must be declared at the global scope (the top level of your script), not inside functions or conditional blocks. This requirement exists because the execution model needs to initialize and track these variables across all bars from the beginning of the chart data.

OHLCV Data Structure

The most common timeseries you'll work with is OHLCV data, representing Open, High, Low, Close, and Volume for each bar. When you call ohlcv(symbol, exchange), you receive a timeseries with multiple fields accessible via dot notation.

Each field of OHLCV data is itself a complete timeseries. When you write ohlcvData.close, you extract the closing price timeseries. You can then use this in calculations, pass it to indicators, or plot it. The field access is not just getting a single value — it's extracting an entire temporal sequence.

IndexFieldDescription
0timestampUnix timestamp (milliseconds)
1openOpening price for the bar
2highHighest price during the bar
3lowLowest price during the bar
4closeClosing price for the bar
5volumeTrading volume during the bar

Understanding this structure is crucial because many technical indicators expect specific fields. For example, sma(source, period) needs a single-dimensional timeseries, so you pass ohlcvData.close rather than the entire OHLCV structure.

Arrays and Collections

Arrays in kScript provide a way to store multiple values of the same type in a single variable. While kScript's primary focus is on timeseries data, arrays serve important roles in configuration, storing multiple parameters, and managing sets of related values.

Array Type System

kScript supports typed arrays: number[], string[], and boolean[]. Arrays are homogeneous, meaning all elements must be of the same type. You cannot create an array like [1, "hello", true] that mixes types — this will cause a compilation error.

Array literals are created using square brackets with comma-separated values: [1, 2, 3, 4, 5] creates a number[] array. The type is inferred from the elements, so all elements must be compatible with the inferred type.

Array usage
//@version=2

define("Multiple Moving Averages", "onchart", true);

timeseries trade = ohlcv(symbol=currentSymbol, exchange=currentExchange);
timeseries close = trade.close;

// Array of periods for multiple MAs
var maPeriods = [10, 20, 50, 200];  // Type: number[]

// Array of colors for plotting
var colors = ["#FF6B35", "#004E89", "#00BA88", "#8E44AD"];  // Type: string[]

// Calculate multiple moving averages
var ma10 = sma(close, 10);
var ma20 = sma(close, 20);
var ma50 = sma(close, 50);

plotLine(value=ma10, width=2, colors=colors, colorIndex=0, label=["MA 10"], desc=["10-period Moving Average"]);
plotLine(value=ma20, width=2, colors=colors, colorIndex=1, label=["MA 20"], desc=["20-period Moving Average"]);
plotLine(value=ma50, width=2, colors=colors, colorIndex=2, label=["MA 50"], desc=["50-period Moving Average"]);

In this example, maPeriods is a number[] array used to configure multiple moving averages with different periods. The colors array provides color strings for plotting each moving average. Arrays are particularly useful when you need to handle multiple similar configurations or iterate over a set of values.

Array Constraints

Arrays in kScript have several constraints that differ from general-purpose languages. First, array size is typically fixed at declaration — you cannot dynamically resize arrays or push/pop elements. Second, all elements must be of the same type, enforced at compile time.

Third, arrays are passed by reference, meaning if you pass an array to a function and the function modifies it, those changes affect the original array. This is different from primitive values, which are copied. Understanding this distinction is important to avoid unintended side effects.

Data Sources and Access Methods

kScript v2 provides extensive access to market data through data sources — functions that retrieve time-series information from exchanges and data providers. Understanding the available data sources and how to access them is fundamental to building sophisticated trading indicators.

Two Access Methods

kScript offers two ways to retrieve data from sources: direct functions and the universal source() function. Direct functions provide dedicated names for each data type, making code more readable and self-documenting. The source() function offers a unified interface using a string identifier to specify which data to fetch.

Method 1: Direct Functions (Recommended)

Direct functions have names matching the data type: ohlcv(), funding_rate(), liquidations(), etc. Each function returns a timeseries of the specified data type. This approach is recommended because it's clearer and provides better compile-time checking.

Direct functions
//@version=2

define("Direct Functions Demo", "offchart", true);

// Using direct, named functions for each data type
timeseries spotPrice = ohlcv(symbol=currentSymbol, exchange=currentExchange);
timeseries fundingData = funding_rate(symbol=currentSymbol, exchange=currentExchange);
timeseries liqData = liquidations(symbol=currentSymbol, exchange=currentExchange);

// Each function returns a timeseries of that specific data type
var close = spotPrice.close;
plotLine(value=close, width=2, colors=["blue"], label=["Spot Price"], desc=["Spot Price Close"]);

Method 2: Universal source() Function

The source() function accepts a string identifier as its first parameter to specify what data to fetch. This method is more dynamic but less type-safe, as the string identifier is not checked until runtime.

source() function
//@version=2

define("Source Function Demo", "offchart", true);

// Using the universal source() function with string identifiers
timeseries spotPrice = source("ohlcv", currentSymbol, currentExchange);
timeseries fundingData = source("funding_rate", currentSymbol, currentExchange);
timeseries liqData = source("liquidations", currentSymbol, currentExchange);

// Produces identical results to direct functions
var close = spotPrice.close;
plotLine(value=close, width=2, colors=["blue"], label=["Spot Price"], desc=["Spot Price Close"]);

Both methods produce identical results. The choice is primarily stylistic, though direct functions are generally preferred for their clarity and compile-time validation. Use source() when you need dynamic data source selection based on user input or configuration.

Available Data Sources

kScript v2 provides access to many different data sources spanning multiple categories of market data. These sources go far beyond basic price information, including derivatives metrics, options data, lending rates, and institutional flows. Understanding what's available allows you to build more sophisticated analytical tools.

Data sources are organized into six primary categories:

CategoryDescriptionExamples
Price & VolumeCore market data including OHLCV, orderbook dynamics, and trading volume analysisohlcv, buy_sell_volume, orderbook
Derivatives & FuturesFunding rates, liquidations, open interest, and perpetual futures metricsfunding_rate, liquidations, open_interest, cme_oi, long_short_ratio
Options & VolatilityOptions trading data, implied volatility surfaces, and volatility indicesoptions_volume, options_open_interest, deribit_implied_volatility, deribit_volatility_index, skew
Lending & MarginMargin borrowing rates, lending pool sizes, and credit market databitfinex_margin_rate, bitfinex_active_credit_size, bitfinex_credit_size, bitfinex_funding_size
Institutional & ETFETF flows and holdings, institutional position data, and treasury balancesetf_flow, etf_holding, etf_premium_rate, binance_treasury_balance
Protocols & DeFiProtocol-specific metrics, DeFi positions, and smart contract dataethena_positions

Multi-Source Analysis Example

One of kScript's strengths is the ability to combine multiple data sources in a single indicator. By analyzing price action alongside derivatives metrics, liquidation data, and funding rates, you can build more comprehensive market views.

Multi-source analysis
//@version=2

define("Multi-Source Analysis", "offchart", true);

// Access multiple data sources
timeseries spotPrices = ohlcv(symbol=currentSymbol, exchange=currentExchange);
timeseries fundingRates = funding_rate(symbol=currentSymbol, exchange=currentExchange);
timeseries liquidationData = liquidations(symbol=currentSymbol, exchange=currentExchange);

// Extract specific fields
timeseries close = spotPrices.close;
timeseries fundingRate = fundingRates;  // Funding is single-dimensional

// Calculate indicators combining multiple sources
var priceMA = sma(close, 20);
var avgFunding = sma(fundingRate, 24);  // 24-hour average funding

// Detect high funding with recent liquidations
var highFunding = (avgFunding > 0.01);  // 1% funding rate
var recentLiq = (liquidationData > 0);  // Any liquidations

// Plot the funding rate when conditions are met
plotLine(value=(highFunding && recentLiq ? fundingRate : na), width=2, colors=["red"], label=["Funding Rate"], desc=["Funding Rate Signal"]);

This example demonstrates accessing three different data sources and combining them in analysis. The spot price provides the baseline market price, funding rates indicate the perpetual futures premium/discount, and liquidation data shows forced position closures. Together, these create a multi-dimensional view of market conditions.

Type Conversion and Casting

kScript's type system features minimal automatic type conversion. Unlike languages like JavaScript that aggressively coerce types, kScript requires explicit type compatibility. This strictness prevents subtle bugs and makes code behavior more predictable.

Conversion Rules

TimeSeries to Number: When you call a technical indicator function like sma(source, period) on a timeseries, it returns a number — the indicator's value at the current bar. The function extracts the current value from the timeseries automatically. This conversion is implicit and happens whenever an indicator processes historical data to produce a current value.

Number to TimeSeries: Arithmetic operations between a timeseries and a number produce a new timeseries. For example, close + 100 creates a timeseries where every value is 100 more than the corresponding close price. The scalar value is broadcast across all bars. Similarly, close * 1.05 produces a timeseries with all values scaled by 5%.

No String Coercion: Strings are never automatically converted to numbers or vice versa. If a function expects a string parameter, you must provide a string. If it expects a number, you must provide a number. This prevents errors like accidentally passing a number where a symbol string was expected.

Type conversion examples
//@version=2

define("Type Conversion Examples", "offchart", true);

timeseries trade = ohlcv(symbol=currentSymbol, exchange=currentExchange);
timeseries close = trade.close;
timeseries volume = trade.volume;

// TimeSeries → Number: sma() returns a number (value at current bar)
var sma20 = sma(close, 20);  // Type: number

// Number → TimeSeries: arithmetic with timeseries produces timeseries
timeseries adjusted = close + 100;  // Type: timeseries (100 added to each bar)

// TimeSeries ↔ TimeSeries: operations between timeseries
timeseries deviation = close - sma20;  // Type: timeseries

// Scalar broadcast: number is broadcast across all bars
timeseries volumeScaled = volume * 1.5;  // Type: timeseries

plotLine(value=deviation, width=2, colors=["green", "red"], label=["Deviation"], desc=["Price Deviation"]);

This example shows several type conversions. The sma() function returns a number (the indicator value at the current bar), even though it operates on a timeseries. The subtraction close - sma20 produces a new timeseries because one operand is a timeseries. The multiplication volume * 1.5 also produces a timeseries.

Best Practices and Patterns

Writing effective kScript code requires understanding not just the type system rules, but also the patterns and practices that lead to clear, maintainable, and efficient indicators. These best practices emerge from the type system's design and kScript's execution model.

Descriptive Variable Names

Choose variable names that indicate both the data type and the purpose. For timeseries variables, names like closes, volumes, or prices (plural) suggest they contain multiple values. For scalar variables, singular names like period, threshold, or crossover indicate single values.

When extracting fields from OHLCV data, use names that reflect the field: var close = ohlcvData.close; is clearer than var c = ohlcvData.close;. The slight verbosity pays dividends in code readability, especially when multiple data sources are involved.

Cache Data References

When using data source fields in indicators, pass the field reference directly rather than creating intermediate timeseries variables. This avoids creating unnecessary timeseries objects and keeps your code more efficient. Simply pass trade.close directly to indicator functions like sma(trade.close, period).

//@version=2
define("Best Practices Demo", "onchart", true);
// ✅ Get data source once
timeseries trade = ohlcv(symbol=currentSymbol, exchange=currentExchange);
// ✅ Use descriptive variable names
var shortPeriod = 10;
var longPeriod = 20;
var volumeThreshold = 1000;
// ✅ Pass field references directly - no intermediate timeseries needed
// Good: sma(trade.close, period) - passes reference directly
// Avoid: timeseries close = trade.close; sma(close, period) - creates extra object
var fastMA = sma(trade.close, shortPeriod);
var slowMA = sma(trade.close, longPeriod);
// ✅ Handle NA values appropriately
var crossover = (fastMA > slowMA) && (fastMA[1] <= slowMA[1]);
var highVolume = (trade.volume > volumeThreshold);
var signal = (crossover && highVolume);
// ✅ Use conditional plotting with NA
var displayValue = (signal ? fastMA : na);
Be Explicit with Declarations

While kScript infers types, you still choose the declaration keyword: var or timeseries. Always use the correct keyword based on what you're storing. This explicitness serves as documentation — readers immediately know whether a variable holds a scalar or historical data without examining the initialization expression.

Group related declarations together and separate different types of declarations with blank lines. Put all timeseries declarations near the top of your script (as required), followed by configuration variables, then derived calculations. This organization makes the data flow clear.

Complete Example: Multi-Source Momentum Indicator

To demonstrate how the type system, data sources, and best practices come together, here's a complete indicator that analyzes momentum using multiple data sources. This example showcases proper type declarations, data source access, type conversions, and conditional plotting.

Multi-Source Momentum Indicator
//@version=2

define("Multi-Source Momentum Indicator", "offchart", true);

// =============================================================================
// DATA SOURCES - Access multiple types of market data
// =============================================================================

// Spot price data
timeseries spotData = ohlcv(symbol=currentSymbol, exchange=currentExchange);
timeseries close = spotData.close;  // Cache for multiple uses

// Funding rate data (perpetual futures)
timeseries fundingData = funding_rate(symbol=currentSymbol, exchange=currentExchange);

// Liquidation data
timeseries liquidationData = liquidations(symbol=currentSymbol, exchange=currentExchange);

// =============================================================================
// CONFIGURATION - User inputs and constants
// =============================================================================

var rsiPeriod = input(name="RSI", label="RSI Period", type="number", defaultValue=14, constraints={min: 2, max: 100});
var fundingThreshold = 0.01;  // 1% funding rate threshold
var extremeRSI = 30;          // RSI threshold for extreme conditions

// =============================================================================
// INDICATORS - Calculate technical indicators
// =============================================================================

// Price momentum using RSI
var rsiSeries = rsi(close, period=rsiPeriod);

// Average funding rate over 24 bars (24 hours for 1h timeframe)
var avgFunding = sma(fundingData, 24);

// Recent liquidation activity
var totalLiq = sma(liquidationData, 6);  // 6-hour liquidation average

// =============================================================================
// SIGNAL LOGIC - Combine multiple sources for signal generation
// =============================================================================

// Detect extreme conditions across multiple dimensions
var extremeRSILevel = (rsiSeries < extremeRSI);           // Oversold on RSI
var negativeFunding = (avgFunding < -fundingThreshold);  // Negative funding (shorts paying)
var highLiquidations = (totalLiq > 0);                    // Recent liquidations occurred

// Combined signal: all conditions must be true
var extremeCondition = extremeRSILevel && negativeFunding && highLiquidations;

// =============================================================================
// VISUALIZATION - Plot with conditional display
// =============================================================================

// Plot RSI in main panel
var colors = ["#2962FF", "#FF6B35"];  // Blue and orange color array
plotLine(value=rsiSeries, width=2, colors=colors, colorIndex=0, label=["RSI"], desc=["Relative Strength Index"]);

// Plot extreme threshold line
plotLine(value=extremeRSI, width=1, colors=colors, colorIndex=1, label=["Extreme RSI"], desc=["Extreme RSI Threshold"]);

// Show signal marker only when extreme condition is met (use 'na' otherwise)
var signalMarker = (extremeCondition ? rsiSeries : na);
plotShape(value=signalMarker, shape="circle", width=4, colors=["#00BA88"], label=["Signal"], desc=["Extreme RSI Signal"]);

This indicator demonstrates several key concepts from the type system documentation:

  • Data Sources: Accesses spot price (OHLCV), funding rate, and liquidation data from multiple sources
  • Type Declarations: Uses timeseries for historical data and var for configuration and calculated values
  • Type Conversion: The sma() function converts timeseries to number; the RSI calculation demonstrates working with both
  • Arrays: Uses a color array for the plot, demonstrating array type string[]
  • Conditional Logic: Uses na to conditionally show the signal marker only during extreme conditions
  • Best Practices: Caches the close reference, uses descriptive names, and includes comments explaining the logic

Note how the types flow naturally through the script: timeseries data sources feed into indicators that return numbers, those numbers are used in comparisons that return booleans, and the booleans control conditional plotting with na. Understanding this type flow is essential to writing effective kScript indicators.