Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 4 additions & 4 deletions docs/api-coverage/color.md
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ parent: API Coverage
| `color.from_gradient()` | ✅ | Create color from gradient |
| `color.new()` | ✅ | Create new color |
| `color.rgb()` | ✅ | Create color from RGB values |
| `color.b()` | | Get blue component |
| `color.g()` | | Get green component |
| `color.r()` | | Get red component |
| `color.t()` | | Get transparency component |
| `color.b()` | | Get blue component |
| `color.g()` | | Get green component |
| `color.r()` | | Get red component |
| `color.t()` | | Get transparency component |
10 changes: 5 additions & 5 deletions docs/api-coverage/pinescript-v6/color.json
Original file line number Diff line number Diff line change
Expand Up @@ -19,12 +19,12 @@
"color.yellow": true
},
"Color Functions": {
"color.b()": false,
"color.b()": true,
"color.from_gradient()": true,
"color.g()": false,
"color.g()": true,
"color.new()": true,
"color.r()": false,
"color.r()": true,
"color.rgb()": true,
"color.t()": false
"color.t()": true
}
}
}
1 change: 1 addition & 0 deletions rollup.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -110,6 +110,7 @@ const BrowserConfigDev = {
format: 'umd',
name: 'PineTSLib',
exports: 'auto',
sourcemap: true,
},
plugins: [
excludeMockProvider(),
Expand Down
8 changes: 8 additions & 0 deletions scripts/generate-matrix-index.js
Original file line number Diff line number Diff line change
Expand Up @@ -133,6 +133,11 @@ ${getters.map((g) => ` ${g}: ${g}`).join(',\n')}
})
.join('\n');

// Type-specific aliases for matrix.new<TYPE>() → matrix.new_TYPE()
// The transpiler rewrites generic type params to method name suffixes
const typeAliases = ['float', 'int', 'string', 'bool', 'color', 'line', 'label', 'box', 'linefill', 'table'];
const typeAliasInstall = typeAliases.map(t => ` this.new_${t} = new_fn(context);`).join('\n');

const classCode = `// SPDX-License-Identifier: AGPL-3.0-only
// This file is auto-generated. Do not edit manually.
// Run: npm run generate:matrix-index
Expand All @@ -146,6 +151,9 @@ export class PineMatrix {
${getterInstall}
// Install methods
${methodInstall}
// Type-specific aliases — used internally by PineTS to handle strong types.
// The transpiler rewrites matrix.new<float>(...) → matrix.new_float(...)
${typeAliasInstall}
}
}

Expand Down
40 changes: 39 additions & 1 deletion src/Context.class.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import { PineMap } from './namespaces/map/map.index';
import { PineMatrix } from './namespaces/matrix/matrix.index';
import { Barstate } from './namespaces/Barstate';
import { Core, NAHelper } from './namespaces/Core';
import { PineColor } from './namespaces/color/PineColor';
import { TimeHelper, TimeComponentHelper, EXTRACTORS, getDatePartsInTimezone } from './namespaces/Time';
import { Input } from './namespaces/input/input.index';
import PineMath from './namespaces/math/math.index';
Expand Down Expand Up @@ -142,12 +143,14 @@ export class Context {
Type: core.Type.bind(core),

na: new NAHelper(),
color: core.color,

nz: core.nz.bind(core),
indicator: core.indicator.bind(core),
fixnan: core.fixnan.bind(core),
alertcondition: core.alertcondition.bind(core),
alert: core.alert.bind(core),
error: core.error.bind(core),
max_bars_back: core.max_bars_back.bind(core),
timestamp: core.timestamp.bind(core),
time: new TimeHelper(this, 'openTime'),
time_close: new TimeHelper(this, 'closeTime'),
Expand Down Expand Up @@ -457,6 +460,41 @@ export class Context {
Object.defineProperty(this.pine['table'], 'all', {
get: () => tableHelper.all,
});

// color namespace
const colorHelper = new PineColor(this);
this.bindContextObject(
colorHelper,
[
'any',
'param',
'new',
'rgb',
'from_gradient',
'r',
'g',
'b',
't',
'aqua',
'black',
'blue',
'fuchsia',
'gray',
'green',
'lime',
'maroon',
'navy',
'olive',
'orange',
'purple',
'red',
'silver',
'teal',
'white',
'yellow',
],
'color',
);
}

/**
Expand Down
6 changes: 6 additions & 0 deletions src/PineTS.class.ts
Original file line number Diff line number Diff line change
Expand Up @@ -342,6 +342,12 @@ export class PineTS {
// #4: Data changed — bump version so secondary contexts know to refresh
context.dataVersion++;

// Update context.length so barstate.islast (which checks
// context.idx === context.length - 1) works correctly for new bars.
// Without this, barstate.islast stays false after new candles arrive,
// and any `if barstate.islast` drawing logic never executes.
context.length = this.data.length;

// Always recalculate last candle + new ones
// Remove last result (will be recalculated with fresh data)
this._removeLastResult(context);
Expand Down
44 changes: 37 additions & 7 deletions src/namespaces/Barstate.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,11 +20,15 @@ export class Barstate {
}

public get ishistory() {
return this.context.idx < this.context.data.close.data.length - 1;
// Use context.length (total bar count) instead of incrementally-built
// context.data.close.data.length, which only has bars 0..idx during
// execution and would always equal idx+1 (making ishistory always false).
return this.context.idx < this.context.length - 1;
}

public get isrealtime() {
return this.context.idx === this.context.data.close.data.length - 1;
// Use context.length for same reason as ishistory above.
return this.context.idx === this.context.length - 1;
}

public get isconfirmed() {
Expand All @@ -36,10 +40,36 @@ export class Barstate {
}

public get islastconfirmedhistory() {
// True when this is the last bar whose close time is in the past
// (the bar right before the current live bar).
const closeTime = this.context.data.closeTime.data[this.context.idx];
const nextCloseTime = this.context.data.closeTime.data[this.context.idx + 1];
return closeTime <= Date.now() && (nextCloseTime === undefined || nextCloseTime > Date.now());
// True on exactly ONE bar: the last confirmed historical bar.
// Per Pine Script docs: "Returns true if script is executing on the
// dataset's last bar when market is closed, or on the bar immediately
// preceding the real-time bar if market is open."
//
// Uses context.length (total bar count, set before iteration) instead
// of the incrementally-built context.data arrays, which only contain
// bars 0..idx during execution and would falsely return true on every bar.
const idx = this.context.idx;
const totalBars = this.context.length;

if (idx === totalBars - 1) {
// Last bar in the dataset — true only if market is closed
// (i.e., this bar's close time is in the past → it's confirmed)
const closeTime = this.context.data.closeTime.data[idx];
return closeTime <= Date.now();
}

if (idx === totalBars - 2) {
// Second-to-last bar — true if the last bar is a live/realtime bar
// (i.e., the last bar's close time is still in the future).
// Read from context.marketData (full raw candle array, available
// before iteration starts) to peek at the last bar's close time.
const lastCloseTime = this.context.marketData?.[totalBars - 1]?.closeTime;
if (lastCloseTime !== undefined) {
return lastCloseTime > Date.now();
}
return false;
}

return false;
}
}
Loading
Loading