From 59363c8f2ef65c165decaeafb854f052ab429a7d Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Mon, 1 Dec 2025 21:28:28 +0000 Subject: [PATCH 1/4] Initial plan From b5fd2a903ad6da6b33e52264d5a3625c845a56ef Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Mon, 1 Dec 2025 21:41:51 +0000 Subject: [PATCH 2/4] Fix transpiler: volume ??, numeric inputs, let vs const, Series init, vwma args, HistoryAccess Co-authored-by: deepentropy <8287111+deepentropy@users.noreply.github.com> --- indicators/adr/adr.ts | 2 +- indicators/bop/bop.ts | 2 +- indicators/dema/dema.ts | 2 +- indicators/hma/hma.ts | 2 +- indicators/lsma/lsma.ts | 38 +++------------- indicators/mass-index/mass-index.ts | 2 +- .../mc-ginley-dynamic/mc-ginley-dynamic.ts | 6 +-- indicators/momentum/momentum.ts | 2 +- indicators/roc/roc.ts | 2 +- indicators/sma/sma.ts | 4 +- indicators/tema/tema.ts | 2 +- oakscriptjs/src/math/index.ts | 29 ++++++++++-- transpiler/src/transpiler/PineToTS.ts | 45 ++++++++++++++++--- 13 files changed, 83 insertions(+), 55 deletions(-) diff --git a/indicators/adr/adr.ts b/indicators/adr/adr.ts index 9942560..5bb6437 100644 --- a/indicators/adr/adr.ts +++ b/indicators/adr/adr.ts @@ -33,7 +33,7 @@ export function Average_Day_Range(bars: any[], inputs: Partial const high = new Series(bars, (bar) => bar.high); const low = new Series(bars, (bar) => bar.low); const close = new Series(bars, (bar) => bar.close); - const volume = new Series(bars, (bar) => bar.volume); + const volume = new Series(bars, (bar) => bar.volume ?? 0); // Calculated price sources const hl2 = high.add(low).div(2); diff --git a/indicators/bop/bop.ts b/indicators/bop/bop.ts index 9e2a5be..96a941b 100644 --- a/indicators/bop/bop.ts +++ b/indicators/bop/bop.ts @@ -23,7 +23,7 @@ export function Balance_of_Power(bars: any[]): IndicatorResult { const high = new Series(bars, (bar) => bar.high); const low = new Series(bars, (bar) => bar.low); const close = new Series(bars, (bar) => bar.close); - const volume = new Series(bars, (bar) => bar.volume); + const volume = new Series(bars, (bar) => bar.volume ?? 0); // Calculated price sources const hl2 = high.add(low).div(2); diff --git a/indicators/dema/dema.ts b/indicators/dema/dema.ts index 05a0400..756e41d 100644 --- a/indicators/dema/dema.ts +++ b/indicators/dema/dema.ts @@ -35,7 +35,7 @@ export function Double_EMA(bars: any[], inputs: Partial = {}): const high = new Series(bars, (bar) => bar.high); const low = new Series(bars, (bar) => bar.low); const close = new Series(bars, (bar) => bar.close); - const volume = new Series(bars, (bar) => bar.volume); + const volume = new Series(bars, (bar) => bar.volume ?? 0); // Calculated price sources const hl2 = high.add(low).div(2); diff --git a/indicators/hma/hma.ts b/indicators/hma/hma.ts index 2392a45..7e6a820 100644 --- a/indicators/hma/hma.ts +++ b/indicators/hma/hma.ts @@ -35,7 +35,7 @@ export function Hull_Moving_Average(bars: any[], inputs: Partial bar.high); const low = new Series(bars, (bar) => bar.low); const close = new Series(bars, (bar) => bar.close); - const volume = new Series(bars, (bar) => bar.volume); + const volume = new Series(bars, (bar) => bar.volume ?? 0); // Calculated price sources const hl2 = high.add(low).div(2); diff --git a/indicators/lsma/lsma.ts b/indicators/lsma/lsma.ts index 1cd3dc5..b6ffd9c 100644 --- a/indicators/lsma/lsma.ts +++ b/indicators/lsma/lsma.ts @@ -18,14 +18,14 @@ interface PlotConfig { } export interface IndicatorInputs { - length: "open" | "high" | "low" | "close" | "hl2" | "hlc3" | "ohlc4" | "hlcc4"; - offset: "open" | "high" | "low" | "close" | "hl2" | "hlc3" | "ohlc4" | "hlcc4"; + length: number; + offset: number; src: "open" | "high" | "low" | "close" | "hl2" | "hlc3" | "ohlc4" | "hlcc4"; } const defaultInputs: IndicatorInputs = { - length: "25", - offset: "0", + length: 25, + offset: 0, src: "close", }; @@ -37,7 +37,7 @@ export function Least_Squares_Moving_Average(bars: any[], inputs: Partial bar.high); const low = new Series(bars, (bar) => bar.low); const close = new Series(bars, (bar) => bar.close); - const volume = new Series(bars, (bar) => bar.volume); + const volume = new Series(bars, (bar) => bar.volume ?? 0); // Calculated price sources const hl2 = high.add(low).div(2); @@ -46,32 +46,6 @@ export function Least_Squares_Moving_Average(bars: any[], inputs: Partial { - switch (length) { - case "open": return open; - case "high": return high; - case "low": return low; - case "close": return close; - case "hl2": return hl2; - case "hlc3": return hlc3; - case "ohlc4": return ohlc4; - case "hlcc4": return hlcc4; - default: return close; - } - })(); - const offsetSeries = (() => { - switch (offset) { - case "open": return open; - case "high": return high; - case "low": return low; - case "close": return close; - case "hl2": return hl2; - case "hlc3": return hlc3; - case "ohlc4": return ohlc4; - case "hlcc4": return hlcc4; - default: return close; - } - })(); const srcSeries = (() => { switch (src) { case "open": return open; @@ -98,7 +72,7 @@ export function Least_Squares_Moving_Average(bars: any[], inputs: Partial = {}): const high = new Series(bars, (bar) => bar.high); const low = new Series(bars, (bar) => bar.low); const close = new Series(bars, (bar) => bar.close); - const volume = new Series(bars, (bar) => bar.volume); + const volume = new Series(bars, (bar) => bar.volume ?? 0); // Calculated price sources const hl2 = high.add(low).div(2); diff --git a/indicators/mc-ginley-dynamic/mc-ginley-dynamic.ts b/indicators/mc-ginley-dynamic/mc-ginley-dynamic.ts index e322967..7cb52c1 100644 --- a/indicators/mc-ginley-dynamic/mc-ginley-dynamic.ts +++ b/indicators/mc-ginley-dynamic/mc-ginley-dynamic.ts @@ -33,7 +33,7 @@ export function McGinley_Dynamic(bars: any[], inputs: Partial = const high = new Series(bars, (bar) => bar.high); const low = new Series(bars, (bar) => bar.low); const close = new Series(bars, (bar) => bar.close); - const volume = new Series(bars, (bar) => bar.volume); + const volume = new Series(bars, (bar) => bar.volume ?? 0); // Calculated price sources const hl2 = high.add(low).div(2); @@ -54,8 +54,8 @@ export function McGinley_Dynamic(bars: any[], inputs: Partial = // @version=6 const source = close; - const mg = 0; - mg = (na(mg.get(1)) ? ta.ema(source, length) : mg.get(1).add(source.sub(mg.get(1))).div((length * math.pow(source.div(mg.get(1)), 4)))); + let mg = new Series(bars, () => 0); + mg = (na(mg.get(1)) ? ta.ema(source, length) : source.sub(mg.get(1)).add(mg.get(1)).div((length * math.pow(source.div(mg.get(1)), 4)))); return { metadata: { title: "McGinley Dynamic", shorttitle: "McGinley Dynamic", overlay: true }, diff --git a/indicators/momentum/momentum.ts b/indicators/momentum/momentum.ts index 0ab4c2b..805a4b6 100644 --- a/indicators/momentum/momentum.ts +++ b/indicators/momentum/momentum.ts @@ -35,7 +35,7 @@ export function Momentum(bars: any[], inputs: Partial = {}): In const high = new Series(bars, (bar) => bar.high); const low = new Series(bars, (bar) => bar.low); const close = new Series(bars, (bar) => bar.close); - const volume = new Series(bars, (bar) => bar.volume); + const volume = new Series(bars, (bar) => bar.volume ?? 0); // Calculated price sources const hl2 = high.add(low).div(2); diff --git a/indicators/roc/roc.ts b/indicators/roc/roc.ts index fbbe14d..3fd27f0 100644 --- a/indicators/roc/roc.ts +++ b/indicators/roc/roc.ts @@ -35,7 +35,7 @@ export function Rate_Of_Change(bars: any[], inputs: Partial = { const high = new Series(bars, (bar) => bar.high); const low = new Series(bars, (bar) => bar.low); const close = new Series(bars, (bar) => bar.close); - const volume = new Series(bars, (bar) => bar.volume); + const volume = new Series(bars, (bar) => bar.volume ?? 0); // Calculated price sources const hl2 = high.add(low).div(2); diff --git a/indicators/sma/sma.ts b/indicators/sma/sma.ts index dd49d8c..d3c815b 100644 --- a/indicators/sma/sma.ts +++ b/indicators/sma/sma.ts @@ -43,7 +43,7 @@ export function Moving_Average_Simple(bars: any[], inputs: Partial bar.high); const low = new Series(bars, (bar) => bar.low); const close = new Series(bars, (bar) => bar.close); - const volume = new Series(bars, (bar) => bar.volume); + const volume = new Series(bars, (bar) => bar.volume ?? 0); // Calculated price sources const hl2 = high.add(low).div(2); @@ -93,7 +93,7 @@ export function Moving_Average_Simple(bars: any[], inputs: Partial = {}): const high = new Series(bars, (bar) => bar.high); const low = new Series(bars, (bar) => bar.low); const close = new Series(bars, (bar) => bar.close); - const volume = new Series(bars, (bar) => bar.volume); + const volume = new Series(bars, (bar) => bar.volume ?? 0); // Calculated price sources const hl2 = high.add(low).div(2); diff --git a/oakscriptjs/src/math/index.ts b/oakscriptjs/src/math/index.ts index 30b7f3c..92a490f 100644 --- a/oakscriptjs/src/math/index.ts +++ b/oakscriptjs/src/math/index.ts @@ -10,6 +10,7 @@ */ import { float, int, simple_int, series_float } from '../types'; +import { Series } from '../runtime/series'; /** * Returns the absolute value of a number. @@ -192,9 +193,9 @@ export function avg(...values: float[]): float { /** * Returns the sum of values over a sliding window. * - * @param source - Series of values + * @param source - Series of values or array * @param length - Window length for summation - * @returns Series with rolling sum values + * @returns Series with rolling sum values or array * * @remarks * - Calculates sum of last `length` values at each point @@ -209,7 +210,29 @@ export function avg(...values: float[]): float { * * @see {@link https://www.tradingview.com/pine-script-reference/v6/#fun_math.sum | PineScript math.sum} */ -export function sum(source: series_float, length: simple_int): series_float { +export function sum(source: series_float | Series, length: simple_int): series_float | Series { + // Handle Series objects + if (source instanceof Series) { + const sourceArray = source.toArray(); + const result: series_float = []; + + for (let i = 0; i < sourceArray.length; i++) { + if (i < length - 1) { + result.push(NaN); + } else { + let total = 0; + for (let j = 0; j < length; j++) { + total += sourceArray[i - j]!; + } + result.push(total); + } + } + + // Return Series object from the result array + return new Series(source.bars, (bar, idx) => result[idx] ?? NaN); + } + + // Handle plain arrays const result: series_float = []; for (let i = 0; i < source.length; i++) { diff --git a/transpiler/src/transpiler/PineToTS.ts b/transpiler/src/transpiler/PineToTS.ts index 4333b51..d2e26a0 100644 --- a/transpiler/src/transpiler/PineToTS.ts +++ b/transpiler/src/transpiler/PineToTS.ts @@ -69,6 +69,7 @@ class CodeGenerator { private indicatorOverlay: boolean = false; private variables: Map = new Map(); private seriesVariables: Set = new Set(); // Track which variables are Series + private reassignedVariables: Set = new Set(); // Track which variables are reassigned private plots: string[] = []; private plotConfigs: Array<{ id: string; title: string; color: string; lineWidth: number }> = []; private inputs: InputDefinition[] = []; @@ -181,7 +182,7 @@ class CodeGenerator { this.emit("const high = new Series(bars, (bar) => bar.high);"); this.emit("const low = new Series(bars, (bar) => bar.low);"); this.emit("const close = new Series(bars, (bar) => bar.close);"); - this.emit("const volume = new Series(bars, (bar) => bar.volume);"); + this.emit("const volume = new Series(bars, (bar) => bar.volume ?? 0);"); this.emit(''); // Mark these as Series variables @@ -831,6 +832,15 @@ class CodeGenerator { } } } + + // Track variables that are reassigned (using := operator) + if (node.type === 'Reassignment' && node.children && node.children.length >= 1) { + const left = node.children[0]; + if (left && left.type === 'Identifier') { + const varName = String(left.value || ''); + this.reassignedVariables.add(varName); + } + } // Detect syminfo usage if (node.type === 'MemberExpression') { @@ -868,7 +878,7 @@ class CodeGenerator { 'input.source': 'source', }; - const inputType = inputTypeMap[funcName]; + let inputType = inputTypeMap[funcName]; if (!inputType) return null; const input: InputDefinition = { @@ -920,6 +930,11 @@ class CodeGenerator { input.title = String(args[1].value || ''); } + // For plain input(), infer type from defval if it's a number + if (funcName === 'input' && typeof input.defval === 'number') { + input.inputType = Number.isInteger(input.defval) ? 'int' : 'float'; + } + return input; } @@ -1204,7 +1219,7 @@ class CodeGenerator { const name = String(left.value || 'unknown'); const tsName = this.sanitizeIdentifier(name); - const rightExpr = this.generateExpression(right); + let rightExpr = this.generateExpression(right); // Skip assignment if right side is empty (e.g., unsupported functions) if (!rightExpr || rightExpr.trim() === '') { @@ -1214,11 +1229,21 @@ class CodeGenerator { } // Check if the right side is a Series expression - const rightIsSeries = this.isSeriesExpression(right); + let rightIsSeries = this.isSeriesExpression(right); + + // Special case: if this variable will be reassigned and the right side is a numeric literal, + // convert it to a Series initialized with that constant value + if (this.reassignedVariables.has(name) && right.type === 'NumberLiteral') { + const numValue = Number(right.value); + rightExpr = `new Series(bars, () => ${numValue})`; + rightIsSeries = true; + } if (!this.variables.has(name)) { this.variables.set(name, tsName); - this.emit(`const ${tsName} = ${rightExpr};`); + // Use 'let' if this variable will be reassigned, otherwise use 'const' + const declKeyword = this.reassignedVariables.has(name) ? 'let' : 'const'; + this.emit(`${declKeyword} ${tsName} = ${rightExpr};`); // If assigning a Series expression, mark this variable as a Series if (rightIsSeries) { this.seriesVariables.add(tsName); @@ -1774,6 +1799,12 @@ class CodeGenerator { return ''; } + // Handle ta.vwma specially - it needs volume parameter + if (name === 'ta.vwma') { + // ta.vwma(source, length) should become ta.vwma(source, length, volume) + return `ta.vwma(${args}, volume)`; + } + // Translate common function names const translated = this.translateFunctionName(name); return `${translated}(${args})`; @@ -1860,8 +1891,8 @@ class CodeGenerator { } if (node.type === 'HistoryAccess') { - // History access (e.g., src[1]) returns a Series element, but the base should be a Series - return true; + // History access (e.g., src[1]) returns a scalar value, not a Series + return false; } if (node.type === 'BinaryExpression') { From 7808326d2a9c6f996c175335f0d15a7d9101feb0 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Mon, 1 Dec 2025 21:45:37 +0000 Subject: [PATCH 3/4] Add Series.bars getter, fix math.sum to handle Series, manual fixes for generated code Co-authored-by: deepentropy <8287111+deepentropy@users.noreply.github.com> --- indicators/mass-index/mass-index.ts | 2 +- .../mc-ginley-dynamic/mc-ginley-dynamic.ts | 4 ++- indicators/sma/sma.ts | 6 ++-- oakscriptjs/src/math/index.ts | 36 ++++++------------- oakscriptjs/src/runtime/series.ts | 8 +++++ 5 files changed, 26 insertions(+), 30 deletions(-) diff --git a/indicators/mass-index/mass-index.ts b/indicators/mass-index/mass-index.ts index c01f0ad..c53d1fe 100644 --- a/indicators/mass-index/mass-index.ts +++ b/indicators/mass-index/mass-index.ts @@ -54,7 +54,7 @@ export function Mass_Index(bars: any[], inputs: Partial = {}): // @version=6 const span = high.sub(low); - const mi = math.sum(ta.ema(span, 9).div(ta.ema(ta.ema(span, 9), 9)), length); + const mi = math.sum(ta.ema(span, 9).div(ta.ema(ta.ema(span, 9), 9)), length) as Series; return { metadata: { title: "Mass Index", shorttitle: "Mass Index", overlay: false }, diff --git a/indicators/mc-ginley-dynamic/mc-ginley-dynamic.ts b/indicators/mc-ginley-dynamic/mc-ginley-dynamic.ts index 7cb52c1..9489058 100644 --- a/indicators/mc-ginley-dynamic/mc-ginley-dynamic.ts +++ b/indicators/mc-ginley-dynamic/mc-ginley-dynamic.ts @@ -55,7 +55,9 @@ export function McGinley_Dynamic(bars: any[], inputs: Partial = // @version=6 const source = close; let mg = new Series(bars, () => 0); - mg = (na(mg.get(1)) ? ta.ema(source, length) : source.sub(mg.get(1)).add(mg.get(1)).div((length * math.pow(source.div(mg.get(1)), 4)))); + // Note: math.pow needs to be called element-wise on the series + // This is a simplified version for now + mg = (na(mg.get(1)) ? ta.ema(source, length) : source.sub(mg.get(1)).add(mg.get(1)).div((length * 4))); return { metadata: { title: "McGinley Dynamic", shorttitle: "McGinley Dynamic", overlay: true }, diff --git a/indicators/sma/sma.ts b/indicators/sma/sma.ts index d3c815b..f0cd2dd 100644 --- a/indicators/sma/sma.ts +++ b/indicators/sma/sma.ts @@ -98,14 +98,14 @@ export function Moving_Average_Simple(bars: any[], inputs: Partial NaN)); + const smoothingStDev = (isBB ? ta.stdev(out, maLengthInput).mul(bbMultInput) : new Series(bars, () => NaN)); // bbUpperBand = ; // bbLowerBand = ; return { metadata: { title: "Moving Average Simple", shorttitle: "SMA", overlay: true }, - plots: { 'plot0': out.toArray().map((v: number | undefined, i: number) => ({ time: bars[i]!.time, value: v ?? NaN })), 'plot1': smoothingMA.toArray().map((v: number | undefined, i: number) => ({ time: bars[i]!.time, value: v ?? NaN })), 'plot2': (smoothingMA + smoothingStDev).toArray().map((v: number | undefined, i: number) => ({ time: bars[i]!.time, value: v ?? NaN })), 'plot3': (smoothingMA - smoothingStDev).toArray().map((v: number | undefined, i: number) => ({ time: bars[i]!.time, value: v ?? NaN })) }, + plots: { 'plot0': out.toArray().map((v: number | undefined, i: number) => ({ time: bars[i]!.time, value: v ?? NaN })), 'plot1': smoothingMA.toArray().map((v: number | undefined, i: number) => ({ time: bars[i]!.time, value: v ?? NaN })), 'plot2': smoothingMA.add(smoothingStDev).toArray().map((v: number | undefined, i: number) => ({ time: bars[i]!.time, value: v ?? NaN })), 'plot3': smoothingMA.sub(smoothingStDev).toArray().map((v: number | undefined, i: number) => ({ time: bars[i]!.time, value: v ?? NaN })) }, }; } diff --git a/oakscriptjs/src/math/index.ts b/oakscriptjs/src/math/index.ts index 92a490f..0c1ac6b 100644 --- a/oakscriptjs/src/math/index.ts +++ b/oakscriptjs/src/math/index.ts @@ -195,12 +195,13 @@ export function avg(...values: float[]): float { * * @param source - Series of values or array * @param length - Window length for summation - * @returns Series with rolling sum values or array + * @returns Series or array with rolling sum values * * @remarks * - Calculates sum of last `length` values at each point * - Returns NaN for first `length-1` values (insufficient data) * - Window slides forward one value at a time + * - When passed a Series, returns a Series; when passed an array, returns an array * * @example * ```typescript @@ -211,42 +212,27 @@ export function avg(...values: float[]): float { * @see {@link https://www.tradingview.com/pine-script-reference/v6/#fun_math.sum | PineScript math.sum} */ export function sum(source: series_float | Series, length: simple_int): series_float | Series { - // Handle Series objects - if (source instanceof Series) { - const sourceArray = source.toArray(); - const result: series_float = []; - - for (let i = 0; i < sourceArray.length; i++) { - if (i < length - 1) { - result.push(NaN); - } else { - let total = 0; - for (let j = 0; j < length; j++) { - total += sourceArray[i - j]!; - } - result.push(total); - } - } - - // Return Series object from the result array - return new Series(source.bars, (bar, idx) => result[idx] ?? NaN); - } - - // Handle plain arrays + // Handle Series objects - extract to array first + const sourceArray = source instanceof Series ? source.toArray() : source; const result: series_float = []; - for (let i = 0; i < source.length; i++) { + for (let i = 0; i < sourceArray.length; i++) { if (i < length - 1) { result.push(NaN); } else { let total = 0; for (let j = 0; j < length; j++) { - total += source[i - j]!; + total += sourceArray[i - j]!; } result.push(total); } } + // If input was a Series, return a Series + if (source instanceof Series) { + return Series.fromArray(source.bars, result); + } + return result; } diff --git a/oakscriptjs/src/runtime/series.ts b/oakscriptjs/src/runtime/series.ts index 16fe8f2..9335340 100644 --- a/oakscriptjs/src/runtime/series.ts +++ b/oakscriptjs/src/runtime/series.ts @@ -41,6 +41,14 @@ export class Series { this.extractor = extractor; } + /** + * Get the underlying bar data + * @returns Bar array + */ + get bars(): Bar[] { + return this.data; + } + // ============================================ // Static Factory Methods // ============================================ From 5932bd4fa2bba5de56d255afe47fa730d84f748e Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Mon, 1 Dec 2025 21:49:46 +0000 Subject: [PATCH 4/4] Fix transpiler: ternary with na wraps in Series, isSeriesExpression handles ternary Co-authored-by: deepentropy <8287111+deepentropy@users.noreply.github.com> --- indicators/mass-index/mass-index.ts | 2 +- .../mc-ginley-dynamic/mc-ginley-dynamic.ts | 4 +- indicators/sma/sma.ts | 2 +- transpiler/src/transpiler/PineToTS.ts | 44 +++++++++++++++++-- 4 files changed, 44 insertions(+), 8 deletions(-) diff --git a/indicators/mass-index/mass-index.ts b/indicators/mass-index/mass-index.ts index c53d1fe..c01f0ad 100644 --- a/indicators/mass-index/mass-index.ts +++ b/indicators/mass-index/mass-index.ts @@ -54,7 +54,7 @@ export function Mass_Index(bars: any[], inputs: Partial = {}): // @version=6 const span = high.sub(low); - const mi = math.sum(ta.ema(span, 9).div(ta.ema(ta.ema(span, 9), 9)), length) as Series; + const mi = math.sum(ta.ema(span, 9).div(ta.ema(ta.ema(span, 9), 9)), length); return { metadata: { title: "Mass Index", shorttitle: "Mass Index", overlay: false }, diff --git a/indicators/mc-ginley-dynamic/mc-ginley-dynamic.ts b/indicators/mc-ginley-dynamic/mc-ginley-dynamic.ts index 9489058..7cb52c1 100644 --- a/indicators/mc-ginley-dynamic/mc-ginley-dynamic.ts +++ b/indicators/mc-ginley-dynamic/mc-ginley-dynamic.ts @@ -55,9 +55,7 @@ export function McGinley_Dynamic(bars: any[], inputs: Partial = // @version=6 const source = close; let mg = new Series(bars, () => 0); - // Note: math.pow needs to be called element-wise on the series - // This is a simplified version for now - mg = (na(mg.get(1)) ? ta.ema(source, length) : source.sub(mg.get(1)).add(mg.get(1)).div((length * 4))); + mg = (na(mg.get(1)) ? ta.ema(source, length) : source.sub(mg.get(1)).add(mg.get(1)).div((length * math.pow(source.div(mg.get(1)), 4)))); return { metadata: { title: "McGinley Dynamic", shorttitle: "McGinley Dynamic", overlay: true }, diff --git a/indicators/sma/sma.ts b/indicators/sma/sma.ts index f0cd2dd..7521af7 100644 --- a/indicators/sma/sma.ts +++ b/indicators/sma/sma.ts @@ -105,7 +105,7 @@ export function Moving_Average_Simple(bars: any[], inputs: Partial ({ time: bars[i]!.time, value: v ?? NaN })), 'plot1': smoothingMA.toArray().map((v: number | undefined, i: number) => ({ time: bars[i]!.time, value: v ?? NaN })), 'plot2': smoothingMA.add(smoothingStDev).toArray().map((v: number | undefined, i: number) => ({ time: bars[i]!.time, value: v ?? NaN })), 'plot3': smoothingMA.sub(smoothingStDev).toArray().map((v: number | undefined, i: number) => ({ time: bars[i]!.time, value: v ?? NaN })) }, + plots: { 'plot0': out.toArray().map((v: number | undefined, i: number) => ({ time: bars[i]!.time, value: v ?? NaN })), 'plot1': smoothingMA.toArray().map((v: number | undefined, i: number) => ({ time: bars[i]!.time, value: v ?? NaN })), 'plot2': smoothingStDev.add(smoothingMA).toArray().map((v: number | undefined, i: number) => ({ time: bars[i]!.time, value: v ?? NaN })), 'plot3': smoothingMA.sub(smoothingStDev).toArray().map((v: number | undefined, i: number) => ({ time: bars[i]!.time, value: v ?? NaN })) }, }; } diff --git a/transpiler/src/transpiler/PineToTS.ts b/transpiler/src/transpiler/PineToTS.ts index d2e26a0..18a8825 100644 --- a/transpiler/src/transpiler/PineToTS.ts +++ b/transpiler/src/transpiler/PineToTS.ts @@ -1666,9 +1666,38 @@ class CodeGenerator { private generateTernaryExpression(node: ASTNode): string { if (!node.children || node.children.length < 3) return ''; - const condition = this.generateExpression(node.children[0]!); - const consequent = this.generateExpression(node.children[1]!); - const alternate = this.generateExpression(node.children[2]!); + const conditionNode = node.children[0]!; + const consequentNode = node.children[1]!; + const alternateNode = node.children[2]!; + + const condition = this.generateExpression(conditionNode); + let consequent = this.generateExpression(consequentNode); + let alternate = this.generateExpression(alternateNode); + + // Check if one branch is a Series and the other is a scalar + const consequentIsSeries = this.isSeriesExpression(consequentNode); + const alternateIsSeries = this.isSeriesExpression(alternateNode); + + // Special handling for 'na' (which becomes NaN) + const alternateIsNa = alternateNode.type === 'Identifier' && String(alternateNode.value) === 'na'; + const consequentIsNa = consequentNode.type === 'Identifier' && String(consequentNode.value) === 'na'; + + // If one branch is definitely a Series and the other is NaN or a scalar constant, + // wrap the scalar in a Series to maintain type consistency + if (consequentIsSeries && (alternateIsNa || (!alternateIsSeries && alternateNode.type === 'NumberLiteral'))) { + // Wrap alternate in a Series with constant value + alternate = `new Series(bars, () => ${alternate})`; + } else if (alternateIsSeries && (consequentIsNa || (!consequentIsSeries && consequentNode.type === 'NumberLiteral'))) { + // Wrap consequent in a Series with constant value + consequent = `new Series(bars, () => ${consequent})`; + } else if (alternateIsNa && !consequentIsNa) { + // If we're not sure about consequent but alternate is na, assume consequent might be Series + // This is a heuristic - if alternate is na, likely consequent is a Series + alternate = `new Series(bars, () => ${alternate})`; + } else if (consequentIsNa && !alternateIsNa) { + // Similarly for consequent is na + consequent = `new Series(bars, () => ${consequent})`; + } return `(${condition} ? ${consequent} : ${alternate})`; } @@ -1900,6 +1929,15 @@ class CodeGenerator { return true; } + if (node.type === 'TernaryExpression') { + // Ternary expressions return a Series if either branch is a Series + if (node.children && node.children.length >= 3) { + const consequentIsSeries = this.isSeriesExpression(node.children[1]!); + const alternateIsSeries = this.isSeriesExpression(node.children[2]!); + return consequentIsSeries || alternateIsSeries; + } + } + return false; }