PineTS Technical Analysis (ta) Namespace
This directory contains the implementation of Pine Script’s ta.* namespace functions. These functions are designed to replicate Pine Script’s behavior in a JavaScript environment, with a strong focus on incremental calculation and Series compatibility.
Architecture Overview
Each TA function is implemented as a factory function that takes the execution context and returns the actual calculation function. This closure pattern allows functions to access the global state and other context methods efficiently.
export function ema(context: any) {
return (source: any, period: any, callId?: string) => {
// Implementation...
};
}
The param() Method
The param() method is a critical component of the PineTS transpiler system. It is automatically injected by the transpiler to wrap arguments passed to namespace functions.
Purpose
- Normalization: Converts various input types (raw values, arrays, existing Series) into a unified
Seriesobject. - Lookback Handling: Manages the offset logic when accessing historical data (e.g.,
close[1]). - Caching: Generates unique IDs (
p0,p1, etc.) to cache Series objects and avoid redundant object creation.
How it Works
When you write ta.ema(close, 14) in PineTS code, the transpiler converts it to:
ta.ema(ta.param(close, undefined, 'p0'), ta.param(14, undefined, 'p1'), '_ta0');
Inside a TA function implementation, inputs are always unwrapped using Series.from():
const value = Series.from(source).get(0); // Get current value
const length = Series.from(period).get(0); // Get period
Incremental Calculation
PineTS uses incremental calculation to process time-series data efficiently. Instead of recalculating indicators over the entire history for every new bar, functions maintain state and update it with the new bar’s data.
State Management
All TA functions store their state in context.taState. A unique callId (generated by the transpiler) is used as the key to isolate state for different calls to the same function.
Example (EMA):
// Unique key for this specific function call
const stateKey = _callId || `ema_${period}`;
// Initialize state if not present
if (!context.taState[stateKey]) {
context.taState[stateKey] = { prevEma: null, initSum: 0, initCount: 0 };
}
const state = context.taState[stateKey];
// Update state with new value
const ema = calculateEma(currentValue, state.prevEma);
state.prevEma = ema;
Benefits
- Performance: O(1) calculation per bar for most indicators.
- Memory Efficiency: Only necessary state (e.g., previous value, running sum) is stored, not full history arrays for intermediate steps.
Implementation Specifics
1. Tuple Returns (Double Bracket Convention)
When a Pine Script function returns a tuple (multiple values), PineTS requires a specific return format to ensure it’s treated as a single “element” of a Series, rather than an array of values spread across time.
Rule: Return tuples wrapped in double brackets.
// Pine Script: [macd, signal, hist] = ta.macd(...)
// PineTS Implementation:
return [[macdLine, signalLine, histLine]];
- Inner Array: The actual tuple values
[a, b, c]. - Outer Array: Wraps the tuple so the Series initialization logic (
$.init()) treats the inner array as a single value for the current bar.
2. Precision
All numeric returns should be formatted using context.precision(). This ensures consistent decimal handling matching Pine Script defaults (usually 10 decimals).
return context.precision(result);
3. NaN Handling
Functions must gracefully handle NaN inputs and initialization phases.
- Input: Check for
NaNbefore updating state to avoid corruption (e.g.,initSum += NaNresults in permanentNaN). - Output: Return
NaNduring the initialization phase (warm-up period) before enough data is available.
4. Unique Call IDs
The optional _callId parameter is crucial. It allows multiple calls to the same function (e.g., ta.ema(close, 14) called in two different places) to maintain separate states. The transpiler automatically injects these IDs.
Example Implementation Structure
import { Series } from '../../../Series';
export function myIndicator(context: any) {
return (source: any, length: any, _callId?: string) => {
// 1. Unwrap inputs
const period = Series.from(length).get(0);
// 2. Initialize State
if (!context.taState) context.taState = {};
const stateKey = _callId || `myInd_${period}`;
if (!context.taState[stateKey]) {
context.taState[stateKey] = {
/* initial state */
};
}
// 3. Calculate
const current = Series.from(source).get(0);
// ... calculation logic ...
// 4. Return result
return context.precision(result);
};
}
Getter-Like Methods in TA Namespace
Some Pine Script TA functions can be accessed both as properties and as methods. PineTS handles this through transpiler transformation.
Example: ta.tr (True Range)
Pine Script behavior:
// As property (getter)
tr1 = ta.tr // Uses default behavior
// As method with parameter
tr2 = ta.tr(true) // handle_na = true
tr3 = ta.tr(false) // handle_na = false
PineTS implementation:
export function tr(context: any) {
return (handle_na?: any) => {
// Default to true for backward compatibility
const handleNa = handle_na !== undefined ? Series.from(handle_na).get(0) : true;
const high0 = context.get(context.data.high, 0);
const low0 = context.get(context.data.low, 0);
const close1 = context.get(context.data.close, 1);
if (isNaN(close1)) {
return handleNa ? high0 - low0 : NaN;
}
return Math.max(high0 - low0, Math.abs(high0 - close1), Math.abs(low0 - close1));
};
}
Usage in PineTS:
// All these work correctly:
const tr1 = ta.tr; // Auto-converted to ta.tr()
const tr2 = ta.tr(); // Explicit call with default
const tr3 = ta.tr(true); // Explicit parameter
const tr4 = ta.tr(false); // Different behavior
Implementation Guidelines
When implementing getter-like methods:
- Always implement as a method in
methods/directory (notgetters/) - Use optional parameters with sensible defaults
- Document the default behavior in comments
- Maintain backward compatibility when converting from getters
The transpiler automatically handles the conversion from property access to method calls for all known namespaces (ta, math, request, array, input).
Generating the Barrel File
When you add a new method (e.g., methods/newIndicator.ts), you must regenerate the namespace index file (ta.index.ts) to export it.
Command:
npm run generate:ta-index
This script automatically:
- Scans the
methods/andgetters/directories. - Generates imports for all detected files.
- Updates
ta.index.tsto register the new functions in theTechnicalAnalysisclass.
Note: The getters/ directory is maintained for backward compatibility but new getter-like functions should be implemented as methods with optional parameters.