Overview
VibeTrader uses PineTS to execute PineScript indicators directly in the browser. This enables powerful technical analysis without server-side computation.
Architecture
PineTS Provider Pattern
VibeTrader implements a custom data provider that feeds market data to PineTS:
src/lib/domain/TSerProvider.ts
export class TSerProvider {
data : Kline [];
constructor ( kvar : TVar < Kline >) {
this . data = kvar . toArray ();
}
async getMarketData ( tickerId : string , timeframe : string , limit ?: number , sDate ?: number , eDate ?: number ) : Promise < unknown > {
return this . data ;
}
getSymbolInfo ( tickerId : string ) {
return Promise . resolve ({
ticker: tickerId ,
tickerid: tickerId ,
// ... additional symbol metadata
});
}
}
Data Flow
Fetch Market Data - Load OHLCV data via DataFetcher.ts:248
Create Provider - Instantiate TSerProvider with kline data at KlineViewContainer.tsx:264
Initialize PineTS - Create PineTS instance with provider at KlineViewContainer.tsx:266
Execute Scripts - Run PineScript code and get plot results at KlineViewContainer.tsx:268
Store Results - Convert plot data to internal format at KlineViewContainer.tsx:287
Running PineScript
Basic Execution
const provider = new TSerProvider ( kvar );
const pineTS = new PineTS ( provider , ticker , tframeToPineTimeframe ( tframe ));
return pineTS . ready (). then (() =>
pineTS . run ( script ). then ( result => ({ scriptName , result }))
. catch ( error => {
console . error ( error );
throw error ;
})
);
Processing Results
PineTS returns plots with data and options:
KlineViewContainer.tsx:284
const { overlayIndicators , stackedIndicators } =
results . reduce (({ overlayIndicators , stackedIndicators }, { scriptName , result }, n ) => {
if ( result ) {
const tvar = baseSer . varOf ( ` ${ scriptName } _ ${ n } ` ) as TVar < PineData []>;
const plots = Object . values ( result . plots ) as Plot [];
const data = plots . map (({ data }) => data );
// Store plot data in time series
for ( let i = 0 ; i < size ; i ++ ) {
const vs = data . map ( v => v ? v [ i ] : undefined );
tvar . setByIndex ( i , vs );
}
// Categorize as overlay or stacked indicator
const isOverlayIndicator = result . indicator ?. overlay ;
// ... categorization logic
}
return { overlayIndicators , stackedIndicators };
}, init );
PineScript Data Types
VibeTrader defines TypeScript types for PineScript output:
src/lib/domain/PineData.ts
export type PineData = {
title ?: string ,
time : number ,
value : number | boolean | LineObject [],
options ?: { color : string }
}
export type LineObject = {
id : number ;
x1 : number ;
y1 : number ;
x2 : number ;
y2 : number ;
xloc : string ;
extend : string ;
color : string ;
style : string ;
width : number ;
force_overlay : boolean ;
_deleted : boolean ;
}
Timeframe Conversion
PineScript uses different timeframe notation than VibeTrader’s internal representation:
src/lib/domain/PineData.ts:31
export function tframeToPineTimeframe ( tframe : TFrame ) {
const shortName = tframe . shortName
if ( shortName . endsWith ( 'D' )) {
return 'D'
} else if ( shortName . endsWith ( 'W' )) {
return 'W'
} else if ( shortName . endsWith ( 'M' )) {
return 'M'
} else if ( shortName . endsWith ( 'h' ) || shortName . endsWith ( 'H' )) {
return shortName . slice ( 0 , shortName . length )
}
}
Supported timeframes: ['1', '3', '5', '15', '30', '45', '60', '120', '180', '240', 'D', 'W', 'M']
Loading Scripts
From Files
Predefined scripts are loaded from the public/indicators/ directory:
KlineViewContainer.tsx:212
fetchOPredefinedScripts = ( scriptNames : string []) => {
const fetchScript = ( scriptName : string ) =>
fetch ( "./indicators/" + scriptName + ".pine" )
. then ( r => r . text ())
. then ( script => ({ scriptName , script }))
return Promise . all ( scriptNames . map ( scriptName => fetchScript ( scriptName )))
}
Dynamic Execution
Scripts can be executed dynamically at runtime:
KlineViewContainer.tsx:963
analyze ( ticker : string , timeframe : string , scripts ?: string [], tzone ?: string ) {
this . scripts = scripts ?. map (( script , i ) => ({
scriptName: `ai_ ${ Math . round ( 1000 ) } _ ${ i } ` ,
script
}));
return this . fetchData_runScripts ( undefined , 1000 );
}
Plot Categorization
Overlay vs Stacked
Plots are categorized based on their properties:
KlineViewContainer.tsx:308
const isOverlayIndicator = indicator !== undefined && indicator . overlay
const isOverlayOutputShapeAndLocation = (
( style === 'shape' || style === 'char' ) &&
( location === 'abovebar' || location === 'belowbar' )
)
if ( isOverlayOutputShapeAndLocation || isForceOverlay ) {
overlayOutputs . push ( output )
} else if ( notForceOverlay ) {
stackedOutputs . push ( output )
} else {
if ( isOverlayIndicator ) {
overlayOutputs . push ( output )
} else {
stackedOutputs . push ( output )
}
}
Use force_overlay: true in plot options to force an indicator onto the main chart.
Best Practices
Always wrap PineTS execution in try-catch blocks: pineTS . run ( script )
. then ( result => ({ scriptName , result }))
. catch ( error => {
console . error ( error );
throw error ;
})
Clear timeout handlers when unmounting components
Reset scripts when changing symbols/timeframes
Use Promise.all() for parallel script execution
Next Steps
Custom Indicators Learn how to create custom PineScript indicators
Plot Types Understand different plot styles and drawing tools