Debugging Guide
This guide covers practical debugging techniques for PineTS development.
Viewing Transpiled Code
To see what the transpiler generates:
const transformer = transpile.bind(context);
const transpiledFn = transformer(userCode);
console.log(transpiledFn.toString());
This shows the actual JavaScript that will execute, helping identify transformation issues.
Inspecting Context State
Market Data (Forward Storage)
// Check raw market data (forward arrays: oldest to newest)
const close = context.data.close;
console.log('Close array:', close);
console.log('Current close:', close[close.length - 1]); // Last element = current
console.log('Previous close:', close[close.length - 2]); // Second-to-last = previous
console.log('Oldest close:', close[0]); // First element = oldest
Using $.get() (Pine Script Semantics)
// Pine Script-style access (reverse indexing)
console.log('close[0] (current):', context.get(close, 0));
console.log('close[1] (previous):', context.get(close, 1));
console.log('close[2] (2 bars ago):', context.get(close, 2));
User Variables
// Check user-defined variables
console.log('All let variables:', context.let);
console.log('All const variables:', context.const);
console.log('All var variables:', context.var);
// Check specific variable history (forward array)
console.log('ema9 series:', context.let.glb1_ema9);
console.log('ema9 current:', context.let.glb1_ema9[context.let.glb1_ema9.length - 1]);
Function State
// Check TA function internal state
console.log('All TA state:', context.taState);
console.log('Specific EMA state:', context.taState['_ta0']);
Parameter Cache
// Check param() transformed values
console.log('All params:', context.params);
console.log('Specific param:', context.params['p0']);
Inspecting Series Objects
// Check Series wrapper details
const series = context.ta.param(close, 1, 'test');
console.log('Series data (forward array):', series.data);
console.log('Series offset (lookback):', series.offset);
console.log('Series.get(0):', series.get(0)); // Value with offset applied
console.log('Actual index accessed:', series.data.length - 1 - series.offset);
Common Debug Patterns
Pattern 1: Value Mismatch
Problem: Calculated value doesn’t match expected.
Debug:
// In your TA function
export function myIndicator(context: any) {
return (source: any, period: any, _callId?: string) => {
const currentValue = Series.from(source).get(0);
console.log(`[${_callId}] Current value:`, currentValue);
const periodValue = Series.from(period).get(0);
console.log(`[${_callId}] Period:`, periodValue);
const state = context.taState[_callId];
console.log(`[${_callId}] State:`, state);
// ... calculation
const result = /* ... */;
console.log(`[${_callId}] Result:`, result);
return result;
};
}
Pattern 2: Incorrect Array Access
Problem: Getting wrong historical values.
Debug:
const close = context.data.close;
// Forward storage check
console.log('Total bars:', close.length);
console.log('Last 5 (newest to oldest):', [
close[close.length - 1], // Current
close[close.length - 2], // Previous
close[close.length - 3],
close[close.length - 4],
close[close.length - 5],
]);
// Pine Script access check
console.log('Last 5 via $.get():', [
context.get(close, 0), // Current
context.get(close, 1), // Previous
context.get(close, 2),
context.get(close, 3),
context.get(close, 4),
]);
// These should match!
Pattern 3: State Corruption
Problem: Function state becomes NaN or corrupted.
Debug:
export function ema(context: any) {
return (source: any, period: any, _callId?: string) => {
const stateKey = _callId || `ema_${period}`;
const state = context.taState[stateKey];
const currentValue = Series.from(source).get(0);
// Debug NaN propagation
if (isNaN(currentValue)) {
console.warn(`[${stateKey}] NaN input detected at idx ${context.idx}`);
}
if (state && isNaN(state.prevEma)) {
console.error(`[${stateKey}] State corrupted! prevEma is NaN`);
console.log('State:', state);
}
// ... rest of calculation
};
}
Pattern 4: Scope Issues
Problem: Variable not found or accessing wrong scope.
Debug:
// Check what variables exist in each scope
console.log(
'Global variables (glb1_*):',
Object.keys(context.let).filter((k) => k.startsWith('glb1_'))
);
console.log(
'Function scope variables (fn*):',
Object.keys(context.let).filter((k) => k.match(/^fn\d+_/))
);
console.log(
'If scope variables (if*):',
Object.keys(context.let).filter((k) => k.match(/^if\d+_/))
);
Debugging Transpiler Issues
Check Scope Manager State
// In transformer code
console.log('Current scope:', scopeManager.currentScope);
console.log('Scope tree:', scopeManager.scopeTree);
console.log('Variable mapping:', scopeManager.variableMap);
console.log('Context-bound vars:', scopeManager.contextBoundVariables);
Trace AST Transformation
// Add logging to transformer
transform(node: any) {
console.log('Transforming:', node.type, node);
const result = this.transformNode(node);
console.log('Result:', result);
return result;
}
Performance Debugging
Measure Execution Time
const startTime = performance.now();
const result = await pineTS.run(code);
const endTime = performance.now();
console.log(`Execution time: ${endTime - startTime}ms`);
Check Memory Usage
const memBefore = process.memoryUsage();
const result = await pineTS.run(code);
const memAfter = process.memoryUsage();
console.log('Memory delta:', {
heapUsed: (memAfter.heapUsed - memBefore.heapUsed) / 1024 / 1024,
external: (memAfter.external - memBefore.external) / 1024 / 1024,
});
Profile TA Function Calls
// Wrap TA function with timing
const originalEma = context.ta.ema;
const calls = { count: 0, totalTime: 0 };
context.ta.ema = (...args) => {
const start = performance.now();
const result = originalEma(...args);
const elapsed = performance.now() - start;
calls.count++;
calls.totalTime += elapsed;
return result;
};
// After execution
console.log(`EMA called ${calls.count} times, avg: ${calls.totalTime / calls.count}ms`);
Common Issues and Solutions
Issue: NaN Results
Symptoms: Function returns NaN unexpectedly.
Debug:
// Check inputs
console.log('Source:', Series.from(source).get(0));
console.log('Is NaN?', isNaN(Series.from(source).get(0)));
// Check state
console.log('State:', context.taState[_callId]);
// Check initialization
if (!state.initialized) {
console.log('Still initializing, need more data');
}
Common Causes:
- Not enough data for initialization (e.g., EMA needs
periodbars) - State corruption from
NaNinput - Missing null/undefined checks
Issue: Wrong Historical Values
Symptoms: close[1] doesn’t return previous bar.
Debug:
// Verify storage order
console.log(
'Is forward storage?',
close[0] < close[close.length - 1] // Should be true (oldest < newest)
);
// Verify Series offset
const series = Series.from(close);
console.log('Offset:', series.offset); // Should be 0 for close
console.log('get(1) index:', series.data.length - 1 - (series.offset + 1));
Common Causes:
- Using direct array access instead of
$.get() - Confusing forward storage with reverse access
- Incorrect offset in Series wrapper
Issue: State Shared Between Calls
Symptoms: Two ta.ema(close, 9) calls return identical values.
Debug:
// Check call IDs
console.log('Call 1 ID:', _callId1);
console.log('Call 2 ID:', _callId2);
console.log('Are different?', _callId1 !== _callId2); // Should be true
// Check state keys
console.log('State keys:', Object.keys(context.taState));
Common Causes:
- Missing
_callIdparameter in function signature - Not using
_callIdin state key - Transpiler not injecting unique IDs
Testing Strategies
Unit Test Pattern
it('should calculate correctly', async () => {
const pineTS = new PineTS(Provider.Mock, 'BTCUSDC', 'D', null, startDate, endDate);
const sourceCode = (context) => {
const { close } = context.data;
const { ta } = context.pine;
const sma = ta.sma(close, 20);
// Debug output
console.log('Context at idx', context.idx);
console.log('close:', context.get(close, 0));
console.log('sma:', context.get(sma, 0));
return { sma };
};
const { result } = await pineTS.run(sourceCode);
// Assertions
expect(result.sma).toBeDefined();
expect(result.sma[result.sma.length - 1]).toBeCloseTo(expectedValue);
});
Snapshot Testing
// Capture full context state
const snapshot = {
idx: context.idx,
close: context.data.close.slice(-5), // Last 5 bars
variables: { ...context.let },
taState: { ...context.taState },
};
// Compare with expected
expect(snapshot).toMatchSnapshot();
Visual Debugging Tools
Plot All Values
const sourceCode = (context) => {
const { close } = context.data;
const { ta, plotchar } = context.pine;
const sma = ta.sma(close, 20);
const ema = ta.ema(close, 20);
// Plot for visual inspection
plotchar(close, 'close', { color: 'blue' });
plotchar(sma, 'sma', { color: 'red' });
plotchar(ema, 'ema', { color: 'green' });
return { sma, ema };
};
Export to CSV
const { result, plots } = await pineTS.run(sourceCode);
const csv = plots['sma'].data.map((p) => `${new Date(p.time).toISOString()},${p.value}`).join('\n');
fs.writeFileSync('debug.csv', 'time,sma\n' + csv);