You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
Integer parameters of user functions are emitted as 32-bit int, so timestamps passed through them wrap silently #285
A user function that receives a timestamp gets a C++ int parameter, while the variable passed to it is an int64_t. The call narrows the value without any message. A date-window helper fed from input.time() therefore compares time against wrapped dates, and the script places no orders. The same comparison written inline trades normally.
In Pine, int holds millisecond timestamps (time, input.time() values, timestamp() results), so the narrowing changes results silently.
Versions
pineforge-codegen 1.9.1 and 1.9.0 emit the same code (1.6.1 too). The run below used pineforge-engine v1.9.0 (ec44968), Apple clang, macOS arm64. The narrowing is in the generated C++, so it should not depend on the platform.
Repro
//@version=6
strategy('repro')
start = input.time(timestamp('2020-01-01T00:00:00'), 'Start')
stop = input.time(timestamp('2034-07-01T00:00:00'), 'Stop')
inWindow(s, e) => time >= s and time <= e
if inWindow(start, stop) and bar_index % 20 == 0
strategy.entry('L', strategy.long)
if bar_index % 20 == 10
strategy.close('L')
At the call, 1577836800000 (2020-01-01) becomes 1583802368 and 2035324800000 (2034-07-01) becomes -489698304, so time <= e is never true. On a daily series from 2020-01-01 to 2024-01-01 the script places no orders. Writing if time >= start and time <= stop and bar_index % 20 == 0 instead gives 66 trades on the same bars.
The parameter type seems to follow the argument rather than the variable:
Call
Emitted signature
inWindow(start, stop) with input.time() values
bool inWindow(int s, int e)
f(a) => a called as f(1577836800000)
int f(int a)
f(int a) => ... or f(simple int a) => ... with an input.time() value
int parameter
f(t) => t called as f(time)
int64_t f(int64_t t)
Expected
Integer parameters and return values of user functions keep 64 bits (int64_t), as Pine's int does; or, failing that, the narrowing is rejected with an error instead of passing silently.
Three more places where a Pine int becomes a 32-bit C++ int, found on 1.9.1 and unchanged on main c1cc532. Built with pineforge-engine v1.9.1 (fc0a2df) on BTCUSDT 1h bars, 2024-01-01 to 2024-03-01 (1,441 bars); ok read back with // @pf-trace.
A typed int parameter of a method. The untyped form already gets a double.
//@version=6
strategy('repro')
type Stamp
int t = 0
method store(Stamp self, int value) =>
self.t := value
var s = Stamp.new()
s.store(time)
ok = s.t == time ? 1 : 0
// @pf-trace ok=ok
plot(ok)
Generated: int _udt_Stamp_store(Stamp self, int value), while the field is int64_t t. ok is 0 on all 1,441 bars. With method store(Stamp self, value) the signature is (Stamp self, double value) and ok is 1 on every bar.
An array<int> field of a UDT. A top-level array.new<int>() is already std::vector<int64_t>, and storing time in it gives ok 1.
//@version=6
strategy('repro')
type Book
array<int> stamps
var b = Book.new(array.new<int>())
b.stamps.unshift(time)
ok = b.stamps.get(0) == time ? 1 : 0
// @pf-trace ok=ok
plot(ok)
Generated: std::vector<int> stamps and .stamps = std::vector<int>(). ok is 0 on every bar. With array<float> it is 1.
array.from() with an input.time() value. Here the narrowing is caught by the compiler instead of passing silently.
Generated: std::vector<int> stamps; and stamps = std::vector<int>{start};, where start is int64_t. clang: non-constant-expression cannot be narrowed from type 'int64_t' (aka 'long long') to 'int' in initializer list. With array.from(float(start)) it builds and ok is 1.
Expected, as for the cases in the issue: int64_t in all three, since a Pine int holds millisecond UNIX timestamps such as time and input.time() values (time), and ok is 1 on every bar. Checked on TradingView (BINANCE:BTCUSDT 1h): all three scripts compile and run, with no error or warning, and ok is 1 on every bar, as in their controls.
Workarounds, in the same order: leave the method parameter untyped, use array<float> in the UDT, and wrap the value in float() for array.from().
Thank you for the detailed report. We are checking each case against TradingView's behaviour and our source, and will post a result for each case in this issue by the end of 2026-11-03 (UTC). If a case needs longer, we will say so here with the reason.
Fixed in #322, which is merged to main and ships in 1.10.0.
What changed: time values now keep 64 bits through user-function parameters (typed, untyped and simple), typed integer method parameters, array<int> fields of user-defined types, and array.from(...) with a time input, including their copies, tuples and history reads. Several of these shapes stopped the C++ build before (the compiler refused the narrowing) and now compile and run. A missing integer value passed into a method parameter stays missing.
Checks that ran: 54 behaviour tests run each case and compare the stored value with a reference computed in the same script; a comparison of 110 recorded TradingView scripts shows 65 now equal where the previous version differed, 20 equal on both, and none worse than before. TradingView's own integer is a 64-bit floating-point value above 2^53, so values above that are not promised to be exact integers; the target here is "no 32-bit narrowing".
Known limits (separate defects, not changed here; they are also listed in the PR): two script shapes that stopped with a build failure before now build and run but print a value that differs from TradingView. A script that keeps an alias of an array past its source prints 2 where TradingView prints 3, and a copy of a slice that still shares elements prints 2 where TradingView prints 1 (0 of 26 compared bars equal in both). We are working on both separately.
Activity
letiep97 commentedon Nov 1, 2026
Three more places where a Pine
intbecomes a 32-bit C++int, found on 1.9.1 and unchanged on mainc1cc532. Built with pineforge-engine v1.9.1 (fc0a2df) on BTCUSDT 1h bars, 2024-01-01 to 2024-03-01 (1,441 bars);okread back with// @pf-trace.A typed
intparameter of a method. The untyped form already gets adouble.Generated:
int _udt_Stamp_store(Stamp self, int value), while the field isint64_t t.okis 0 on all 1,441 bars. Withmethod store(Stamp self, value)the signature is(Stamp self, double value)andokis 1 on every bar.An
array<int>field of a UDT. A top-levelarray.new<int>()is alreadystd::vector<int64_t>, and storingtimein it givesok1.Generated:
std::vector<int> stampsand.stamps = std::vector<int>().okis 0 on every bar. Witharray<float>it is 1.array.from()with aninput.time()value. Here the narrowing is caught by the compiler instead of passing silently.Generated:
std::vector<int> stamps;andstamps = std::vector<int>{start};, wherestartisint64_t. clang:non-constant-expression cannot be narrowed from type 'int64_t' (aka 'long long') to 'int' in initializer list. Witharray.from(float(start))it builds andokis 1.Expected, as for the cases in the issue:
int64_tin all three, since a Pineintholds millisecond UNIX timestamps such astimeandinput.time()values (time), andokis 1 on every bar. Checked on TradingView (BINANCE:BTCUSDT 1h): all three scripts compile and run, with no error or warning, andokis 1 on every bar, as in their controls.Workarounds, in the same order: leave the method parameter untyped, use
array<float>in the UDT, and wrap the value infloat()forarray.from().luisleo526 commentedon Nov 1, 2026
Thank you for the detailed report. We are checking each case against TradingView's behaviour and our source, and will post a result for each case in this issue by the end of 2026-11-03 (UTC). If a case needs longer, we will say so here with the reason.
luisleo526 commentedon Nov 3, 2026
Fixed in #322, which is merged to main and ships in 1.10.0.
What changed: time values now keep 64 bits through user-function parameters (typed, untyped and
simple), typed integer method parameters,array<int>fields of user-defined types, andarray.from(...)with a time input, including their copies, tuples and history reads. Several of these shapes stopped the C++ build before (the compiler refused the narrowing) and now compile and run. A missing integer value passed into a method parameter stays missing.Checks that ran: 54 behaviour tests run each case and compare the stored value with a reference computed in the same script; a comparison of 110 recorded TradingView scripts shows 65 now equal where the previous version differed, 20 equal on both, and none worse than before. TradingView's own integer is a 64-bit floating-point value above 2^53, so values above that are not promised to be exact integers; the target here is "no 32-bit narrowing".
Known limits (separate defects, not changed here; they are also listed in the PR): two script shapes that stopped with a build failure before now build and run but print a value that differs from TradingView. A script that keeps an alias of an array past its source prints 2 where TradingView prints 3, and a copy of a slice that still shares elements prints 2 where TradingView prints 1 (0 of 26 compared bars equal in both). We are working on both separately.
Thank you for the report.
luisleo526 commentedon Nov 3, 2026
This fix is released in v1.10.0: https://github.com/pineforge-4pass/pineforge-codegen-oss/releases/tag/v1.10.0