Typed Stores
Keyed lookup for Pine Script without choosing the data structure yourself. A store maps integer keys to one value each, and picks the fastest lookup for the keys it was given.
One value per key
There is one store per value type: op.intIntStore(), op.intFloatStore(), op.intBoolStore(), op.intStringStore() and op.intColorStore(). build() pairs the entries at matching positions of a key array and a value array:
var op.IntFloatStore scores = op.intFloatStore()
if barstate.isfirst
// Four entries are shown for readability; both arrays may be much larger.
scores.build(
array.from(410, 411, 412, 413),
array.from(0.80, 0.30, 0.95, 0.50))
float selected = scores.get(412)
// Output: integer key 412 resolves to float value 0.95.Lookup is one-way: get(412) returns 0.95, but values may repeat, so there is no general reverse lookup. Batch-build IDs to scores, states or metadata, then query them without committing to a representation.
clear(), has no lookup yet. In strict mode, the default, querying it stops the script with find() before buildFinish(). Building with empty arrays is fine and gives an empty store.What automatic mode chooses
Every store owns an IntIndex. When the build completes, it looks at the keys and selects a lookup. The rules apply in this order:
| Keys | Lookup | Cost per query |
|---|---|---|
| Consecutive and ascending, such as 410, 411, 412 | Direct arithmetic indexing, no table at all | O(1) |
| A compact range: the span fits within twice the entry count and 16,384 | A dense lookup table | O(1) |
| Unordered, up to 50,000 keys | A native map | O(1) |
| Ascending but sparse | Binary search, or a map when the expected queries justify building one | O(log n), or O(1) |
In our tests, direct integer addressing ran 21% faster than a map, and a map ran 91% faster than repeated linear search with 32 entries. Beyond Pine's 50,000-pair map limit, the store keeps working through binary search, up to 100,000 keys.
A linear scan is available for workloads that rebuild far more often than they query. Automatic mode only picks it when linearMaxEntries is configured, since it is 0 by default.
The expectedQueries hint
build(keys, values, expectedQueries) takes an optional third argument: how many lookups to expect before the next build. A map costs more to build than binary search, so for ascending sparse keys the store builds one only when the queries pay for it, at least 32 or a tenth of the entry count, whichever is larger. Leaving the hint out favours the cheaper build.
When future query volume is unknown, create the store with op.indexConfigDynamic(). It starts with binary search and promotes itself to a map once the counted queries reach the same threshold.
var op.IntStringStore names = op.intStringStore(op.indexConfigDynamic())Misses and batch reads
get(key, fallback)returns the fallback on a miss:naby default, orfalsefor a bool store.- When a stored value may equal the fallback,
read(key)returns[found, value], andcontains(key)tests the key alone. getMany(queryKeys, output)resolves a whole batch with one dispatch, in query order, with the empty value marking each miss.
var op.IntFloatStore scores = op.intFloatStore()
var array<float> picked = array.new<float>()
if barstate.isfirst
scores.build(
array.from(410, 411, 412, 413),
array.from(0.80, 0.30, 0.95, 0.50))
scores.getMany(array.from(413, 999, 410), picked)
// Output: picked is [0.50, na, 0.80]. Key 999 is a miss.Pass the same var output array every time; it is cleared and refilled instead of allocated.
Changing values
set(key, value)overwrites the value of a key that exists and returns true. It cannot add a key: the key set changes only through anotherbuild(), which replaces the whole contents.- Each store keeps a content revision. It advances on every build, on a
set()that changes a value, and on aclear()that discarded something. Writing the same value again leaves it alone. - Pass
revision()towatch.watchInt()orgate.dueWhenChanged()to rebuild something only when the store changed. See Revision & CadenceGate. valuesView()is the stored values in slot order, as a read-only view valid until the next build.
Store, IntIndex or IntBuckets
| You need | Use |
|---|---|
| One primitive value per key | A typed store |
| Several payload fields per key, in arrays you own | IntIndex, which returns the slot for a key |
| Several integers per key, such as the objects in a price cell | IntBuckets |
| Keys that appear and disappear one at a time | StablePool |
Calls
Every store type has the same calls. Each constructor takes an optional IndexConfig.
| Call | What it does |
|---|---|
op.intFloatStore(settings = na) | Creates a store. Also intIntStore(), intBoolStore(), intStringStore() and intColorStore(). |
store.build(keys, values, expectedQueries = na) | Replaces the contents and returns the lookup it chose. Both arrays must be the same length. |
store.get(key, fallback = na) | One value, or the fallback on a miss. |
store.read(key) | Returns [found, value]. |
store.contains(key) | True when the key is present. |
store.getMany(queryKeys, output = na) | Values for a batch of keys, in query order. |
store.set(key, value) | Overwrites an existing key. True when it existed. |
store.size(), store.revision(), store.valuesView(), store.clear() | Entry count, content revision, borrowed values, and emptying. |
Every signature is in the API Reference. Next: IntIndex.