@pineforge/codegen-pyodide 0.8.1 → 0.10.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -258,6 +258,10 @@ class StmtVisitor:
258
258
  # Global-scope non-var vars are class members — emit assignment, not declaration
259
259
  is_global_member = node.name in self._global_member_vars
260
260
 
261
+ def remember_local_type(cpp_type: str | None) -> None:
262
+ if cpp_type and not is_global_member:
263
+ self._current_func_local_types[node.name] = cpp_type
264
+
261
265
  # Check if it is a static (non-series) global member variable already evaluated inside _inputs_initialized_ block
262
266
  is_static_global_input = False
263
267
  if is_global_member and isinstance(node.value, FuncCall) and self._is_input_call(node.value):
@@ -284,7 +288,7 @@ class StmtVisitor:
284
288
  title = self._get_input_title(node.value, var_name=node.name)
285
289
  cpp_val = self._render_input_value(node.value, func_name_i, namespace_i, title)
286
290
  if node.name in self.ctx.series_vars:
287
- lines.append(f"{pad}{safe}.push({cpp_val});")
291
+ self._emit_history_series_write(lines, pad, safe, cpp_val)
288
292
  elif is_global_member:
289
293
  lines.append(f"{pad}{safe} = {cpp_val};")
290
294
  else:
@@ -381,9 +385,12 @@ class StmtVisitor:
381
385
  compute_args = self._ta_compute_args_for_site(site)
382
386
  ret_type = "bool" if self._ta_name_from_site(site) in TA_RETURNS_BOOL else "double"
383
387
  ta_name = self._ta_member_name(site)
384
- ta_expr = f"(is_first_tick_ ? {ta_name}.compute({compute_args}) : {ta_name}.recompute({compute_args}))"
388
+ ta_expr = (
389
+ f"(history_advances_new_bar() ? {ta_name}.compute({compute_args}) "
390
+ f": {ta_name}.recompute({compute_args}))"
391
+ )
385
392
  if node.name in self.ctx.series_vars:
386
- lines.append(f"{pad}{safe}.push({ta_expr});")
393
+ self._emit_history_series_write(lines, pad, safe, ta_expr)
387
394
  elif is_global_member:
388
395
  lines.append(f"{pad}{safe} = {ta_expr};")
389
396
  else:
@@ -393,7 +400,7 @@ class StmtVisitor:
393
400
  # Non-var series variable — push instead of declare
394
401
  if node.name in self.ctx.series_vars:
395
402
  cpp_val = self._visit_expr(node.value)
396
- lines.append(f"{pad}{safe}.push({cpp_val});")
403
+ self._emit_history_series_write(lines, pad, safe, cpp_val)
397
404
  return
398
405
 
399
406
  # If/switch expression: x = if cond ... else ...
@@ -451,12 +458,29 @@ class StmtVisitor:
451
458
  lines.append(f"{pad}{cpp_type}& {safe} = {cpp_val};")
452
459
  return
453
460
 
461
+ # Global-scope UDT array-element alias (BUG: Pine array elements of a
462
+ # user-defined type are references). A global loop-local bound from
463
+ # ``arr.get(i)`` and later field-mutated must ALIAS the element so the
464
+ # mutation writes back into the array. Emit a fresh per-iteration
465
+ # ``UDT& z = arr[i];`` reference (any hoisted class member was
466
+ # suppressed) — identical to the non-hoisted function-local alias path.
467
+ # Gated by _register_udt_array_get_ref_locals (which only records global-
468
+ # scope names); the ``_current_func_body is None`` guard confines this to
469
+ # global scope, so a function-local sharing the name keeps its own path.
470
+ if (node.name in self._udt_array_get_ref_locals
471
+ and getattr(self, "_current_func_body", None) is None):
472
+ udt_t = self._udt_var_types.get(node.name)
473
+ cpp_val = self._visit_rhs_value(node.value, node.name, target_cpp_type=udt_t)
474
+ lines.append(f"{pad}{udt_t}& {safe} = {cpp_val};")
475
+ return
476
+
454
477
  # General declaration
455
478
  cpp_type = self._type_for_decl(node) if not is_global_member else None
456
479
  cpp_val = self._visit_rhs_value(node.value, node.name, target_cpp_type=cpp_type)
457
480
  if is_global_member:
458
481
  lines.append(f"{pad}{safe} = {cpp_val};")
459
482
  else:
483
+ remember_local_type(cpp_type)
460
484
  lines.append(f"{pad}{cpp_type} {safe} = {cpp_val};")
461
485
 
462
486
  @staticmethod
@@ -565,7 +589,14 @@ class StmtVisitor:
565
589
  f"expected {self._type_spec_to_cpp(lhs_spec)}, "
566
590
  f"got {self._type_spec_to_cpp(rhs_spec)}",
567
591
  )
568
- val_cpp = self._visit_rhs_value(node.value, target_name)
592
+ # A bare-``na`` reassignment must adopt the target's declared scalar
593
+ # type (``x := na`` -> ``na<int>()`` not ``na<double>()``); otherwise
594
+ # a double NaN is stored into an int/int64_t/bool member (UB, defeats
595
+ # is_na<T>()). Only computed for bare na — every other RHS is
596
+ # unaffected.
597
+ tct = (self._na_reassign_cpp_type(target_name)
598
+ if self._is_na_expr(node.value) else None)
599
+ val_cpp = self._visit_rhs_value(node.value, target_name, target_cpp_type=tct)
569
600
  if node.op == ":=":
570
601
  lines.append(f"{pad}{safe} = {val_cpp};")
571
602
  else:
@@ -575,7 +606,9 @@ class StmtVisitor:
575
606
  else:
576
607
  lines.append(f"{pad}{safe} {node.op} {val_cpp};")
577
608
  else:
578
- val_cpp = self._visit_rhs_value(node.value, target_name)
609
+ tct = (self._na_reassign_cpp_type(target_name)
610
+ if self._is_na_expr(node.value) else None)
611
+ val_cpp = self._visit_rhs_value(node.value, target_name, target_cpp_type=tct)
579
612
  if node.op == ":=":
580
613
  lines.append(f"{pad}{safe} = {val_cpp};")
581
614
  else:
@@ -591,7 +624,11 @@ class StmtVisitor:
591
624
  compute_args = self._ta_compute_args_for_site(site)
592
625
  ta_mem = self._ta_member_name(site)
593
626
  result_var = f"_result_{ta_mem}"
594
- lines.append(f"{pad}auto {result_var} = (is_first_tick_ ? {ta_mem}.compute({compute_args}) : {ta_mem}.recompute({compute_args}));")
627
+ lines.append(
628
+ f"{pad}auto {result_var} = (history_advances_new_bar() ? "
629
+ f"{ta_mem}.compute({compute_args}) : "
630
+ f"{ta_mem}.recompute({compute_args}));"
631
+ )
595
632
 
596
633
  ta_name = self._ta_name_from_site(site)
597
634
  fields = TA_TUPLE_FIELDS.get(ta_name, [f"field{i}" for i in range(len(node.names))])
@@ -611,7 +648,9 @@ class StmtVisitor:
611
648
  # destructured names keep the plain scalar declaration.
612
649
  if name in self.ctx.series_vars:
613
650
  safe = self._safe_name(name)
614
- lines.append(f"{pad}{safe}.push({field_expr});")
651
+ self._emit_history_series_write(
652
+ lines, pad, safe, field_expr
653
+ )
615
654
  else:
616
655
  lines.append(f"{pad}double {name} = {field_expr};")
617
656
  return
@@ -716,40 +755,37 @@ class StmtVisitor:
716
755
  pad = " " * indent
717
756
  start = self._visit_expr(node.start)
718
757
  end = self._visit_expr(node.end)
719
- var = node.var # new AST uses .var instead of .var_name
720
- if node.step is not None:
721
- # Explicit `by` step: unchanged from before — ascending compare
722
- # (matches every existing corpus use, all positive literal steps).
723
- step = self._visit_expr(node.step)
724
- lines.append(f"{pad}for (int {var} = {start}; {var} <= {end}; {var} += {step}) {{")
725
- else:
726
- # No `by` clause: Pine v6 auto-infers the loop direction from
727
- # start/end — descending (step -1) when start > end, else
728
- # ascending (step +1); see the Pine v6 `for` reference. start/end
729
- # are arbitrary runtime expressions (``for i = array.size(arr)-1
730
- # to 0`` — a common "iterate backward to safely remove an element
731
- # while iterating" idiom), so the direction can't always be
732
- # resolved at codegen time. Compute start/end into locals ONCE
733
- # (avoids re-evaluating a side-effecting expression, same class
734
- # of bug as nz()'s double-eval) and pick the comparison direction
735
- # at runtime from their relative order — this previously always
736
- # emitted an ascending `<=` loop, which never executes when
737
- # start > end (silently dropping the whole loop body).
738
- fid = self._for_counter
739
- self._for_counter += 1
740
- s_var, e_var = f"_for_start_{fid}", f"_for_end_{fid}"
741
- lines.append(f"{pad}int {s_var} = ({start}), {e_var} = ({end});")
742
- lines.append(
743
- f"{pad}for (int {var} = {s_var}; "
744
- f"({s_var} <= {e_var}) ? ({var} <= {e_var}) : ({var} >= {e_var}); "
745
- f"{var} += ({s_var} <= {e_var}) ? 1 : -1) {{"
746
- )
758
+ step = self._visit_expr(node.step) if node.step is not None else "1"
759
+ var = self._safe_name(node.var) # new AST uses .var instead of .var_name
760
+
761
+ # Pine infers loop direction from the initial ``from``/``to`` values for
762
+ # both implicit and explicit ``by`` loops. The ``by`` value is a positive
763
+ # magnitude; descending loops subtract it. ``to`` can change during the
764
+ # loop, so refresh the cached end expression after each iteration while
765
+ # keeping the initial direction and step fixed.
766
+ fid = self._for_counter
767
+ self._for_counter += 1
768
+ s_var = f"_for_start_{fid}"
769
+ e_var = f"_for_end_{fid}"
770
+ step_var = f"_for_step_{fid}"
771
+ down_var = f"_for_down_{fid}"
772
+ lines.append(f"{pad}int {s_var} = ({start});")
773
+ lines.append(f"{pad}int {e_var} = ({end});")
774
+ lines.append(f"{pad}int {step_var} = ({step});")
775
+ lines.append(f"{pad}if ({step_var} < 0) {step_var} = -{step_var};")
776
+ lines.append(f"{pad}if ({step_var} == 0) {step_var} = 1;")
777
+ lines.append(f"{pad}const bool {down_var} = ({s_var} > {e_var});")
778
+ lines.append(
779
+ f"{pad}for (int {var} = {s_var}; "
780
+ f"({down_var} ? ({var} >= {e_var}) : ({var} <= {e_var})); "
781
+ f"{var} += ({down_var} ? -{step_var} : {step_var}), {e_var} = ({end})) {{"
782
+ )
747
783
  # Register the loop counter so reads of it inside the body resolve (the
748
784
  # unknown-identifier guard in _visit_ident would otherwise flag it).
749
785
  saved_loop = self._current_loop_vars
750
786
  self._current_loop_vars = set(self._current_loop_vars)
751
- if var:
752
- self._current_loop_vars.add(var)
787
+ if node.var:
788
+ self._current_loop_vars.add(node.var)
753
789
  _blk_saved = self._push_block_var_remap(node)
754
790
  try:
755
791
  for s in node.body:
@@ -787,9 +823,19 @@ class StmtVisitor:
787
823
  pad = " " * indent
788
824
  iterable = self._visit_expr(node.iterable)
789
825
  saved_loop = self._current_loop_vars
826
+ saved_loop_specs = self._current_loop_var_specs
790
827
  self._current_loop_vars = set(self._current_loop_vars)
828
+ self._current_loop_var_specs = dict(self._current_loop_var_specs)
829
+ iterable_spec = self._type_spec_from_expr(node.iterable)
830
+ elem_spec = (
831
+ iterable_spec.element
832
+ if iterable_spec is not None and iterable_spec.kind == "array"
833
+ else None
834
+ )
791
835
  if node.var:
792
836
  self._current_loop_vars.add(node.var)
837
+ if elem_spec is not None:
838
+ self._current_loop_var_specs[node.var] = elem_spec
793
839
  if node.vars:
794
840
  for v in node.vars:
795
841
  if v != "_":
@@ -809,6 +855,7 @@ class StmtVisitor:
809
855
  self._pop_block_var_remap(_blk_saved)
810
856
  lines.append(f"{pad}}}")
811
857
  self._current_loop_vars = saved_loop
858
+ self._current_loop_var_specs = saved_loop_specs
812
859
 
813
860
  def _visit_while(self, node: WhileStmt, lines: list[str], indent: int) -> None:
814
861
  pad = " " * indent
@@ -166,6 +166,28 @@ class Parser:
166
166
  # ------------------------------------------------------------------
167
167
 
168
168
  def _parse_statement(self):
169
+ stmt = self._parse_single_statement()
170
+ if not self._check(TokenType.COMMA):
171
+ return stmt
172
+
173
+ stmts: list = []
174
+ self._extend_statement_list(stmts, stmt)
175
+ while self._match(TokenType.COMMA):
176
+ if self._check(TokenType.NEWLINE) or self._check(TokenType.DEDENT) or self._at_end():
177
+ break
178
+ self._extend_statement_list(stmts, self._parse_single_statement())
179
+ return stmts
180
+
181
+ @staticmethod
182
+ def _extend_statement_list(stmts: list, stmt) -> None:
183
+ if stmt is None:
184
+ return
185
+ if isinstance(stmt, list):
186
+ stmts.extend(stmt)
187
+ else:
188
+ stmts.append(stmt)
189
+
190
+ def _parse_single_statement(self):
169
191
  cur = self._current()
170
192
 
171
193
  # Control flow keywords
@@ -192,6 +214,33 @@ class Parser:
192
214
  if cur.type in (TokenType.VAR, TokenType.VARIP):
193
215
  return self._parse_var_keyword_decl()
194
216
 
217
+ # Type-qualifier prefix on a top-level declaration. Pine v6 allows
218
+ # ``const`` / ``simple`` / ``series`` to qualify a typed variable
219
+ # declaration (``const int DEFAULT = 1``). The qualifier carries no
220
+ # C++-side meaning (the engine infers mutability from ``var``/
221
+ # ``varip``), so consume it and parse the underlying declaration.
222
+ # Without this the qualifier is parsed as a bare identifier and the
223
+ # subsequent typed decl leaks the stray ``ExprStmt(const)`` that
224
+ # produces "Unknown variable 'const'" at codegen.
225
+ if (cur.type == TokenType.IDENT
226
+ and cur.value in ("const", "series", "simple")):
227
+ nxt = self._peek()
228
+ # ``const int NAME = ...`` / ``const float[] x = ...``
229
+ typed_after_qual = (
230
+ nxt.type in TYPE_KEYWORDS
231
+ or (nxt.type == TokenType.IDENT and self._is_ident_typed_var_decl(offset=1))
232
+ )
233
+ # ``const NAME = ...`` (qualifier on an untyped decl — rare but
234
+ # legal; ``const`` here is just dropped).
235
+ bare_after_qual = (
236
+ nxt.type == TokenType.IDENT
237
+ and self._peek(2).type == TokenType.EQUALS
238
+ and self._peek(3).type != TokenType.EQUALS
239
+ )
240
+ if typed_after_qual or bare_after_qual:
241
+ self._advance() # consume the qualifier prefix
242
+ return self._parse_single_statement()
243
+
195
244
  # Type-annotated declaration: float x = ..., int x = ...
196
245
  if cur.type in TYPE_KEYWORDS and self._peek().type == TokenType.IDENT:
197
246
  # Check that the IDENT is followed by = (not == ) to confirm declaration
@@ -273,7 +322,7 @@ class Parser:
273
322
 
274
323
  return self._set_loc(ExprStmt(expr=expr), start_tok)
275
324
 
276
- def _is_ident_typed_var_decl(self) -> bool:
325
+ def _is_ident_typed_var_decl(self, offset: int = 0) -> bool:
277
326
  """Look ahead for ``IDENT [<...>] IDENT '='`` (UDT-typed declaration).
278
327
 
279
328
  Triggered by ``Sample s = expr`` / ``array<Sample> w = expr`` /
@@ -283,15 +332,21 @@ class Parser:
283
332
  ``true``, ``false``), and function-definition shapes
284
333
  (``IDENT '(' ... ')' '=>'``).
285
334
 
335
+ ``offset`` shifts the starting position (used to look ahead past a
336
+ consumed type-qualifier prefix like ``const``).
337
+
286
338
  Probe: data/validation/udt-method-probe-19-array-of-udt-method.
287
339
  """
288
- cur = self._current()
340
+ base = self.pos + offset
341
+ if base >= len(self.tokens):
342
+ return False
343
+ cur = self.tokens[base]
289
344
  if cur.type != TokenType.IDENT:
290
345
  return False
291
346
  if cur.value in ("enum", "type", "strategy", "indicator", "na", "true", "false"):
292
347
  return False
293
348
  # Skip past optional generic args after the type ident: IDENT [< ... >]
294
- i = self.pos + 1
349
+ i = base + 1
295
350
  if i < len(self.tokens) and self.tokens[i].type == TokenType.LT:
296
351
  depth = 1
297
352
  i += 1
@@ -374,12 +429,24 @@ class Parser:
374
429
  first = VarDecl(name=name_tok.value, value=value)
375
430
  self._set_loc(first, start_tok)
376
431
 
377
- # Check for comma-separated additional declarations: x=1, y=2, z=3
378
- if not self._check(TokenType.COMMA):
432
+ # Check for comma-separated additional declarations: x=1, y=2, z=3.
433
+ # Other comma-separated simple statements (``a := 1, b := 2`` or
434
+ # ``array.fill(a, na), array.set(a, 0, 1)``) are handled by the
435
+ # statement wrapper above, so do not greedily consume their comma.
436
+ if not (
437
+ self._check(TokenType.COMMA)
438
+ and self._peek().type == TokenType.IDENT
439
+ and self._peek(2).type == TokenType.EQUALS
440
+ ):
379
441
  return first
380
442
 
381
443
  decls = [first]
382
- while self._match(TokenType.COMMA):
444
+ while (
445
+ self._check(TokenType.COMMA)
446
+ and self._peek().type == TokenType.IDENT
447
+ and self._peek(2).type == TokenType.EQUALS
448
+ ):
449
+ self._advance()
383
450
  st = self._current()
384
451
  n = self._consume(TokenType.IDENT)
385
452
  self._consume(TokenType.EQUALS)
@@ -133,8 +133,8 @@ _ta("kcw", _sig([("source", F), ("length", I), ("mult", F, 1.5)]))
133
133
  # --- Volatility & Range ---
134
134
  _ta("atr", _sig([("length", I)]))
135
135
  _ta("tr", _sig([("handle_na", B, False)]))
136
- _ta("stdev", _sig([("source", F), ("length", I)]))
137
- _ta("variance",_sig([("source", F), ("length", I)]))
136
+ _ta("stdev", _sig([("source", F), ("length", I), ("biased", B, True)]))
137
+ _ta("variance",_sig([("source", F), ("length", I), ("biased", B, True)]))
138
138
 
139
139
  # --- Trend ---
140
140
  _ta("supertrend", _sig([("factor", F), ("atrPeriod", I)],
@@ -342,10 +342,14 @@ UNSUPPORTED_NAMESPACE_VARS: dict[str, str] = {
342
342
  # request.security parameter rules.
343
343
  # Codegen supports symbol/timeframe/expression plus gaps/lookahead (read in
344
344
  # _eval_security_* emission and forwarded to register_security_eval).
345
- # ignore_invalid_symbol/currency are accepted by the parser but silently
346
- # dropped by codegen — reject loudly to surface unsupported behavior.
345
+ # `currency` is still rejected loudly (it changes the returned values via FX
346
+ # conversion, which codegen drops — silently wrong). `ignore_invalid_symbol` is
347
+ # a guaranteed no-op here: codegen forces request.security onto the current
348
+ # chart symbol (see the "symbol must reference current chart symbol" check),
349
+ # which is always valid, so the flag can never change the result — accept+ignore.
347
350
  SECURITY_ALLOWED_PARAMS: frozenset[str] = frozenset(
348
351
  {"symbol", "timeframe", "expression", "gaps", "lookahead",
352
+ "ignore_invalid_symbol",
349
353
  # Data-adjustment constants — silently accepted and ignored by codegen;
350
354
  # the underlying engine uses a fixed unadjusted data source.
351
355
  "backadjustment", "settlement_as_close", "adjustment"}
@@ -462,6 +466,12 @@ class SupportChecker:
462
466
  # (``panel.cell(...)``) is a visual sink whose args may carry visual
463
467
  # constants, so it routes through ``_visit_children_const_ok``.
464
468
  self._visual_container_vars: set[str] = set()
469
+ # User helpers whose body is only a visual sink (for example
470
+ # ``cell(table t, ..., align) => t.cell(..., text_halign=align)``).
471
+ # A call to such a helper is itself a visual context, so style
472
+ # constants passed through its arguments are safe and should not trip
473
+ # the free-expression constant-namespace rejection.
474
+ self._visual_sink_funcs: set[str] = set()
465
475
  self._drawing_tuple_vars: set[str] = set()
466
476
  self._func_tuple_drawing_returns: dict[str, list[bool]] = {}
467
477
  # Track whether we are inside an if/ternary condition expression.
@@ -520,6 +530,8 @@ class SupportChecker:
520
530
  if tuple_returns:
521
531
  self._func_tuple_drawing_returns[stmt.name] = tuple_returns
522
532
  self._collect_visual_container_params(stmt)
533
+ if self._func_body_is_visual_sink(stmt):
534
+ self._visual_sink_funcs.add(stmt.name)
523
535
  elif isinstance(stmt, MethodDef):
524
536
  self._user_methods.add(stmt.name)
525
537
  self._collect_visual_container_params(stmt)
@@ -535,6 +547,27 @@ class SupportChecker:
535
547
  if hint and str(hint).replace(" ", "") in _VISUAL_CONTAINER_TYPES:
536
548
  self._visual_container_vars.add(pname)
537
549
 
550
+ def _expr_is_visual_sink_call(self, expr: ASTNode | None) -> bool:
551
+ if not isinstance(expr, FuncCall):
552
+ return False
553
+ ns, name = _qualified_name(expr.callee)
554
+ if ns is None and name in SKIP_FUNC_NAMES:
555
+ return True
556
+ if ns is not None and ns in SKIP_NAMESPACES:
557
+ return True
558
+ if ns in _DRAWING_NOOP_BY_NS and name in _DRAWING_NOOP_BY_NS[ns]:
559
+ return True
560
+ return ns is not None and ns in self._visual_container_vars
561
+
562
+ def _func_body_is_visual_sink(self, fn: FuncDef | MethodDef) -> bool:
563
+ if not fn.body:
564
+ return False
565
+ for stmt in fn.body:
566
+ expr = stmt.expr if isinstance(stmt, ExprStmt) else None
567
+ if not self._expr_is_visual_sink_call(expr):
568
+ return False
569
+ return True
570
+
538
571
  @staticmethod
539
572
  def _type_name_contains_drawing(type_name: str | None) -> bool:
540
573
  if not type_name:
@@ -1207,6 +1240,10 @@ class SupportChecker:
1207
1240
  self._visit_children_const_ok(node)
1208
1241
  return
1209
1242
 
1243
+ if ns is None and name in self._visual_sink_funcs:
1244
+ self._visit_children_const_ok(node)
1245
+ return
1246
+
1210
1247
  self._visit_children(node)
1211
1248
 
1212
1249
  def _visit_Identifier(self, node: Identifier) -> None:
Binary file
package/release.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
- "codegen": "0.8.1",
2
+ "codegen": "0.10.0",
3
3
  "pyodide": "314.0.0",
4
4
  "python": "3.14.0",
5
5
  "emscripten": "emscripten_5_0_3",
6
- "sha256": "85ac44383967dd8c9825a276661eab568b16ecb89fcb929a3b9b638b6a4f371c"
6
+ "sha256": "66102b3b3eb6f1e118de1a816f9b56a73311c0340a0a208fd97241f568b43fb1"
7
7
  }
package/tables.json CHANGED
@@ -1,5 +1,5 @@
1
1
  {
2
- "CODEGEN_VERSION": "0.8.1",
2
+ "CODEGEN_VERSION": "0.10.0",
3
3
  "TA_CLASS_MAP_KEYS": [
4
4
  "sma",
5
5
  "ema",
@@ -577,11 +577,13 @@
577
577
  ],
578
578
  "ta.stdev": [
579
579
  "source",
580
- "length"
580
+ "length",
581
+ "biased"
581
582
  ],
582
583
  "ta.variance": [
583
584
  "source",
584
- "length"
585
+ "length",
586
+ "biased"
585
587
  ],
586
588
  "ta.supertrend": [
587
589
  "factor",
@@ -1574,6 +1576,7 @@
1574
1576
  "backadjustment",
1575
1577
  "expression",
1576
1578
  "gaps",
1579
+ "ignore_invalid_symbol",
1577
1580
  "lookahead",
1578
1581
  "settlement_as_close",
1579
1582
  "symbol",
Binary file