Skip to main content
Advanced10 min read
Learn how to create custom, reusable functions in kScript v2 using the func keyword for modular and maintainable code.

Overview

kScript v2 introduces user-defined functions, allowing you to encapsulate logic into reusable modules. Functions can accept both var and timeseries arguments, support both positional and named parameter calling (kwargs), and must follow specific scoping rules.
FeatureDescription
funcKeyword used to define functions
Infinite PossibilitiesCreate any custom logic you need
Global Scope RequiredFunctions must be declared at top level

Function Declaration

Basic Syntax

func functionName(parameter1, parameter2) {
  // Function body
  return result
}

Kwargs Support

User-defined functions support both positional and named parameter calling conventions:
// Function definition
func calculate(base, multiplier, offset) {
  return base * multiplier + offset
}

// Positional calling
var result1 = calculate(10, 2, 5)  // Returns 25

// Named parameter calling (kwargs)
var result2 = calculate(base=10, multiplier=2, offset=5)  // Returns 25
var result3 = calculate(offset=5, base=10, multiplier=2)  // Returns 25

Syntax Details

Function Name: Must follow standard identifier rules (letters, numbers, underscore). Cannot start with a number. Parameters: Can accept var and timeseries arguments. Parameter types are inferred from usage. Support both positional and named parameter calling (kwargs). Return Statement: Functions must explicitly return a value. The return type is inferred from the returned expression.

Function Examples

Simple Calculation

Basic mathematical operations with error handling:
func safeDiv(a, b) {
  return b == 0 ? 0 : a / b
}

// Usage
var result = safeDiv(a=10, b=2)  // Returns 5
var safe = safeDiv(a=10, b=0)    // Returns 0

Average of Two Values

func calculateAverage(a, b) {
  return (a + b) / 2
}

// Usage
var avg = calculateAverage(close[0], close[1])

Custom Indicator Logic

func isGreenCandle(openPrice, closePrice) {
  return closePrice > openPrice
}

func isBullishEngulfing(prevOpen, prevClose, currOpen, currClose) {
  var prevWasRed = prevClose < prevOpen
  var currIsGreen = currClose > currOpen
  var engulfs = currOpen < prevClose && currClose > prevOpen
  return prevWasRed && currIsGreen && engulfs
}

// Usage
timeseries ohlcv = ohlcv(symbol=currentSymbol, exchange=currentExchange)
var bullish = isBullishEngulfing(
  prevOpen=ohlcv.open[1],
  prevClose=ohlcv.close[1],
  currOpen=ohlcv.open[0],
  currClose=ohlcv.close[0]
)

Constraints and Rules

No Timeseries Declarations Inside Functions

Functions cannot declare new timeseries inside their body. All timeseries must be declared in global scope.
// Invalid
func bad() {
  timeseries ts = ohlcv(...)  // Error!
}

// Valid - pass timeseries as parameter
func good(ts) {
  return ts.close[0]
}

Global Scope Only

Functions must be declared in global scope, not inside loops, conditionals, or other functions.
// Invalid
if (condition) {
  func bad() {...}  // Error!
}

// Valid
func good() {...}

if (condition) {
  var result = good()  // OK to call
}

Parameter Types

Functions can accept var and timeseries arguments. Types are inferred from how parameters are used.
// Valid - accepts both var and timeseries
func processData(varParam, tsParam) {
  var current = tsParam[0]  // Treats tsParam as timeseries
  return varParam + current
}

Return Requirement

All functions must have an explicit return statement. The return type is inferred from the expression.
// Valid
func add(a, b) {
  return a + b  // Required
}

// Invalid - no return
func noReturn(a, b) {
  var sum = a + b  // Missing return!
}

Best Practices

Keep Functions Focused

Each function should do one thing well:
// Good - single responsibility
func calculateRSIColor(rsiValue) {
  return rsiValue > 70 ? 0 : (rsiValue < 30 ? 1 : 2)
}

// Avoid - doing too much
func calculateAndPlotRSI(...) {
  // Don't mix calculation with plotting
}

Use Descriptive Names

Function names should clearly indicate what they do:
// Good
func isOverbought(rsiValue) { return rsiValue > 70 }
func calculatePercentChange(oldValue, newValue) {...}

// Avoid
func calc(a, b) {...}
func check(x) {...}

Document with kwargs

Using named parameters makes function calls self-documenting:
// Self-documenting call
var signal = isBullishEngulfing(
  prevOpen=ohlcv.open[1],
  prevClose=ohlcv.close[1],
  currOpen=ohlcv.open[0],
  currClose=ohlcv.close[0]
)