keep signal shapes on the main chart with RSI pane #264
Annotations
6 errors and 1 warning
|
test-and-build
Process completed with exit code 1.
|
|
tests/safety-visual.test.ts > visual plan isolation > keeps the legacy HTF background profile-aware and nearly transparent:
tests/safety-visual.test.ts#L156
AssertionError: expected '//@Version=6\n// Generated determinis…' to contain 'bgcolor(showTrendRibbon and visualPro…'
- Expected
+ Received
- bgcolor(showTrendRibbon and visualProfile != "Clean" ? (htfBull ? color.new(color.green, 99) : color.new(color.red, 99)) : na, title="HTF bias")
+ //@Version=6
+ // Generated deterministically by PineForge Studio
+ // Review and test this script in TradingView before using it with real capital.
+ indicator("PineForge Indicator", overlay=false, max_labels_count=500, max_lines_count=500)
+
+ // === Inputs ===
+ confirmedOnly = input.bool(true, "Confirmed candles only")
+ cooldownBars = input.int(5, "Signal cooldown bars", minval=0)
+ expectedChartTimeframe = input.timeframe("15", "Expected chart timeframe")
+ enforceChartTimeframe = input.bool(true, "Block signals on a different chart timeframe")
+ visualProfile = "Advanced" // Selected in PineForge Studio
+ colorSignalBars = input.bool(true, "Color bars by current setup")
+ showTrendRibbon = input.bool(true, "Show trend ribbon")
+ showRiskOutcomeLabels = input.bool(true, "Show stop/target outcome labels")
+ emaFastLen = input.int(20, "Fast EMA", minval=1)
+ emaSlowLen = input.int(50, "Slow EMA", minval=1)
+ longMaLen = input.int(100, "Long MA", minval=1)
+ rsiLen = input.int(14, "RSI length", minval=2)
+ rsiLongLevel = input.float(55, "RSI long threshold")
+ rsiShortLevel = input.float(45, "RSI short threshold")
+ volumeLen = input.int(20, "Volume average", minval=1)
+ volumeMultiplier = input.float(1, "Volume multiplier", minval=0.1, step=0.05)
+ htf = input.timeframe("240", "Higher timeframe")
+ atrLen = input.int(14, "ATR length", minval=1)
+ atrMultiple = input.float(2, "ATR stop multiple", minval=0.1)
+ riskReward = input.float(2, "Risk/reward", minval=0.1)
+
+ // === Core calculations ===
+ emaFast = ta.ema(close, emaFastLen)
+ emaSlow = ta.ema(close, emaSlowLen)
+ longMa = ta.sma(close, longMaLen)
+ vwapValue = ta.vwap(hlc3)
+ rsiValue = ta.rsi(close, rsiLen)
+ volumeAverage = ta.sma(volume, volumeLen)
+ atrValue = ta.atr(atrLen)
+
+ // Uses the previous confirmed higher-timeframe candle with lookahead_on to avoid future leakage.
+ htfBull = request.security(syminfo.tickerid, htf, close[1] > ta.ema(close, 100)[1], lookahead=barmerge.lookahead_on)
+ htfBear = not htfBull
+
+ // === Filters and triggers ===
+ confirmationOk = not confirmedOnly or barstate.isconfirmed
+ chartTimeframeAliasOk = (timeframe.period == "1D" and expectedChartTimeframe == "D") or (timeframe.period == "D" and expectedChartTimeframe == "1D")
+ chartTimeframeOk = timeframe.period == expectedChartTimeframe or chartTimeframeAliasOk
+ chartTimeframeAllowed = not enforceChartTimeframe or chartTimeframeOk
+ longSetup = emaFast > emaSlow and close > longMa and close > vwapValue and rsiValue >= rsiLongLevel and volume >= volumeAverage * volumeMultiplier and htfBull and confirmationOk
+ shortSetup = emaFast < emaSlow and close < longMa and close < vwapValue and rsiValue <= rsiShortLevel and volume >= volumeAverage * volumeMultiplier and htfBear and confirmationOk
+ longTrigger = ta.crossover(close, emaFast)
+ shortTrigger = ta.crossunder(close, emaFast)
+ var int lastSignalBar = na
+ cooldownOk = na(lastSignalBar) or bar_index - lastSignalBar > cooldownBars
+ longSignal = chartTimeframeAllowed and longSetup and longTrigger and cooldownOk
+ shortSignal = chartTimeframeAllowed and shortSetup and shortTrigger and cooldownOk
+ if longSignal or shortSignal
+ lastSignalBar := bar_index
+
+ // === Visuals ===
+ barcolor(colorSignalBars and visualProfile != "Clean" ? longSetup ? (visualProfile == "Advanced" ? color.new(color.rgb(0, 165, 90), 15) : color.new(color.lime, 60)) : shortSetup ? (visualProfile == "Advanced" ? color.new(color.rgb(220, 50, 60), 15) : color.new(color.red, 60)) : na : na, title="Setup bar color")
+ bgcolor(showTrendRibbon and visualProfile != "Clean" ? (htfBull ? color.new(color.lime, 95) : color.new(color.red, 95)) : na, title="Trend ribbon", force_overlay=true)
+ plot(emaFast, "Fast EMA", color=color.aqua, force_overlay=true)
+ plot(ema
|
|
tests/rsi-pane-preset-coverage.test.ts > integrated RSI pane preset coverage > RSI Divergence Reversal includes the integrated RSI pane:
tests/rsi-pane-preset-coverage.test.ts#L28
AssertionError: expected '//@Version=6\n// Generated determinis…' to contain 'divRsiLength = input.int(14'
- Expected
+ Received
- divRsiLength = input.int(14
+ //@Version=6
+ // Generated deterministically by PineForge Studio
+ // Review and test this script in TradingView before using it with real capital.
+ indicator("RSI Divergence Reversal", overlay=false, max_labels_count=500, max_lines_count=500)
+
+ // === Inputs ===
+ confirmedOnly = input.bool(true, "Confirmed candles only")
+ cooldownBars = input.int(5, "Signal cooldown bars", minval=0)
+ expectedChartTimeframe = input.timeframe("15", "Expected chart timeframe")
+ enforceChartTimeframe = input.bool(true, "Block signals on a different chart timeframe")
+ visualProfile = "Clean" // Selected in PineForge Studio
+ colorSignalBars = input.bool(false, "Color bars by current setup")
+ showTrendRibbon = input.bool(false, "Show trend ribbon")
+ showRiskOutcomeLabels = input.bool(true, "Show stop/target outcome labels")
+ emaFastLen = input.int(20, "Fast EMA", minval=1)
+ emaSlowLen = input.int(50, "Slow EMA", minval=1)
+ longMaLen = input.int(100, "Long MA", minval=1)
+ rsiLen = input.int(14, "RSI length", minval=2)
+ rsiLongLevel = input.float(40, "RSI long threshold")
+ rsiShortLevel = input.float(60, "RSI short threshold")
+ atrLen = input.int(14, "ATR length", minval=1)
+ atrMultiple = input.float(2, "ATR stop multiple", minval=0.1)
+ riskReward = input.float(2, "Risk/reward", minval=0.1)
+
+ // === Core calculations ===
+ emaFast = ta.ema(close, emaFastLen)
+ emaSlow = ta.ema(close, emaSlowLen)
+ longMa = ta.sma(close, longMaLen)
+ rsiValue = ta.rsi(close, rsiLen)
+ atrValue = ta.atr(atrLen)
+
+ // Entry divergence is shared with the integrated RSI pane below.
+
+ // === Integrated RSI divergence pane ===
+ // The pane is included whenever RSI confirmation or divergence is enabled.
+ // Divergence labels are confirmed only after the configured right-side pivot bars close.
+ // Divergence reuses the main RSI period and source.
+ divPivotLeft = input.int(5, "Divergence pivot left", minval=1)
+ divPivotRight = input.int(5, "Divergence pivot right", minval=1)
+ divRangeMinimum = input.int(5, "Divergence minimum pivot range", minval=1)
+ divRangeMaximum = input.int(60, "Divergence maximum pivot range", minval=2)
+ showRegularBullDiv = input.bool(true, "Show regular bullish divergence")
+ showHiddenBullDiv = input.bool(false, "Show hidden bullish divergence")
+ showRegularBearDiv = input.bool(true, "Show regular bearish divergence")
+ showHiddenBearDiv = input.bool(false, "Show hidden bearish divergence")
+
+ divRsi = rsiValue
+ divRegularBullColor = color.green
+ divRegularBearColor = color.red
+ divHiddenBullColor = color.new(color.green, 35)
+ divHiddenBearColor = color.new(color.red, 35)
+ divTransparentColor = color.new(color.white, 100)
+
+ plot(divRsi, "RSI divergence", linewidth=2, color=color.rgb(41, 98, 255))
+ divMiddleLine = hline(50, "RSI middle", color=color.rgb(120, 123, 134), linestyle=hline.style_dotted)
+ divOverboughtLine = hline(70, "RSI overbought", color=color.rgb(120, 123, 134), linestyle=hline.style_dotted)
+ divOversoldLine = hline(30, "RSI oversold", color=color.rgb(120, 123, 134), linestyle=hline.style_dotted)
+ fill(divOverboughtLine, divOversoldLine, color=color.rgb(33, 150, 243, 90), title="RSI divergence background")
+
+ divPivotLowFound = not na(ta.pivotlow(divRsi, divPivotLeft, divPivotRight))
+ divPivotHighFound = not na(ta.pivothigh(divRsi, divPivotLeft, divPivotRight))
+
+ divInRange(condition) =>
+ divBarsSince = ta.barssince(condition)
+ divRangeMinimum <= divBarsSince and divBarsSince <= divRangeMaximum
+
+ divPreviousLowInRange = divInRange(divPivotLowFound[1])
+ divRsiHigherLow = divRsi[divPivotRight] > ta.valuewhen(divPivotLowFound, divRsi[divPivotRight], 1) and divPreviousLowInRange
+ divPriceLowerLow = low[divPivotRight] < ta.valuewhen(divPivotLowFound, low[divPivotRight], 1)
+ divRegularBullAlert = divPriceLowerLow and divRsiHigherLow and divPivotLowFound
+ divRegularBull = showRegularBullDiv and divRegularBullAle
|
|
tests/public-indicator-only.test.ts > public indicator-only product > does not expose an Output or Strategy Tester selector in the public page:
tests/public-indicator-only.test.ts#L27
AssertionError: expected '"use client";\n\nimport { useMemo, us…' not to contain 'Strategy Tester'
- Expected
+ Received
- Strategy Tester
+ "use client";
+
+ import { useMemo, useState } from "react";
+ import { compilePine } from "@/lib/compiler";
+ import { defaultConfig } from "@/lib/defaults";
+ import { explainConfig } from "@/lib/explain";
+ import { presets } from "@/lib/presets";
+ import { toPublicIndicatorConfig } from "@/lib/public-indicator-config";
+ import { applyVisualProfile } from "@/lib/visual-profile-config";
+ import type { StrategyConfig, VisualProfile } from "@/lib/types";
+
+ const clone = <T,>(value: T): T => JSON.parse(JSON.stringify(value));
+
+ const timeframeOptions = ["1", "3", "5", "15", "30", "45", "60", "120", "240", "D", "W", "M"];
+ const maLengths = [5, 8, 9, 10, 12, 14, 20, 21, 25, 34, 50, 55, 89, 100, 144, 200];
+ const rsiLengths = [5, 7, 9, 14, 21, 28];
+ const rsiLevels = [30, 35, 40, 45, 48, 50, 52, 55, 60, 65, 70];
+ const averageLengths = [5, 10, 14, 20, 30, 50, 100];
+ const multipliers = [0.8, 1, 1.1, 1.2, 1.25, 1.5, 2];
+ const cooldowns = [0, 1, 2, 3, 5, 8, 10, 15, 20];
+ const atrMultiples = [1, 1.25, 1.5, 2, 2.5, 3, 4];
+ const percentages = [0.5, 1, 1.5, 2, 3, 5, 8, 10];
+ const riskRewards = [1, 1.5, 2, 2.5, 3, 4, 5];
+ const pivotLengths = [2, 3, 5, 7, 10];
+ const breakoutLengths = [10, 14, 20, 30, 50, 100];
+
+ export default function Home() {
+ const [config, setConfig] = useState<StrategyConfig>(toPublicIndicatorConfig(clone(defaultConfig)));
+ const [selectedPreset, setSelectedPreset] = useState<string>("Custom configuration");
+ const [tab, setTab] = useState<"builder" | "code" | "ai">("builder");
+ const [aiPrompt, setAiPrompt] = useState("");
+ const [aiResult, setAiResult] = useState<any>(null);
+ const [aiLoading, setAiLoading] = useState(false);
+ const publicConfig = useMemo(() => toPublicIndicatorConfig(config), [config]);
+ const code = useMemo(() => compilePine(publicConfig), [publicConfig]);
+ const explanation = useMemo(() => explainConfig(publicConfig), [publicConfig]);
+
+ const setNested = (section: keyof StrategyConfig, key: string, value: unknown) => {
+ setSelectedPreset("Custom configuration");
+ setConfig((current) => toPublicIndicatorConfig({
+ ...current,
+ [section]: typeof current[section] === "object" ? { ...(current[section] as object), [key]: value } : value
+ } as StrategyConfig));
+ };
+
+ const setTop = <K extends keyof StrategyConfig>(key: K, value: StrategyConfig[K]) => {
+ setSelectedPreset("Custom configuration");
+ setConfig((current) => toPublicIndicatorConfig({ ...current, [key]: value }));
+ };
+
+ const choosePreset = (preset: StrategyConfig) => {
+ setConfig(toPublicIndicatorConfig(clone(preset)));
+ setSelectedPreset(preset.name);
+ };
+
+ const applySensitivity = (value: StrategyConfig["sensitivity"]) => {
+ setSelectedPreset("Custom configuration");
+ setConfig((current) => toPublicIndicatorConfig({
+ ...current,
+ sensitivity: value,
+ execution: { ...current.execution, cooldownBars: value === "frequent" ? 2 : value === "balanced" ? 5 : 10 },
+ volume: { ...current.volume, multiplier: value === "frequent" ? 1 : value === "balanced" ? 1.1 : 1.25 },
+ momentum: { ...current.momentum, adxThreshold: value === "frequent" ? 15 : value === "balanced" ? 20 : 25 }
+ }));
+ };
+
+ const chooseVisualProfile = (profile: VisualProfile) => {
+ setSelectedPreset("Custom configuration");
+ setConfig((current) => toPublicIndicatorConfig(applyVisualProfile(current, profile)));
+ };
+
+ const download = () => {
+ const blob = new Blob([code], { type: "text/plain" });
+ const url = URL.createObjectURL(blob);
+ const a = document.createElement("a");
+ a.href = url;
+ a.download = `${publicConfig.name.toLowerCase().replace(/[^a-z0-9]+/g, "-")}.pine`;
+ a.click();
+ URL.revokeObjectURL(url);
+ };
+
+ const askAI = async () => {
+ setAiLoading(true);
+ setAiResult(null);
+
|
|
tests/indicator-accepted-entry.test.ts > accepted indicator entry lifecycle > uses accepted signals for labels, risk entries, dashboard and alerts:
tests/indicator-accepted-entry.test.ts#L24
AssertionError: expected '//@Version=6\n// Generated determinis…' to contain 'alertcondition(acceptedLongSignal, "L…'
- Expected
+ Received
- alertcondition(acceptedLongSignal, "Long signal"
+ //@Version=6
+ // Generated deterministically by PineForge Studio
+ // Review and test this script in TradingView before using it with real capital.
+ indicator("Balanced Intraday", overlay=false, max_labels_count=500, max_lines_count=500)
+
+ // === Inputs ===
+ confirmedOnly = input.bool(true, "Confirmed candles only")
+ cooldownBars = input.int(5, "Signal cooldown bars", minval=0)
+ expectedChartTimeframe = input.timeframe("15", "Expected chart timeframe")
+ enforceChartTimeframe = input.bool(true, "Block signals on a different chart timeframe")
+ visualProfile = "Clean" // Selected in PineForge Studio
+ colorSignalBars = input.bool(false, "Color bars by current setup")
+ showTrendRibbon = input.bool(false, "Show trend ribbon")
+ showRiskOutcomeLabels = input.bool(true, "Show stop/target outcome labels")
+ emaFastLen = input.int(20, "Fast EMA", minval=1)
+ emaSlowLen = input.int(50, "Slow EMA", minval=1)
+ longMaLen = input.int(100, "Long MA", minval=1)
+ rsiLen = input.int(14, "RSI length", minval=2)
+ rsiLongLevel = input.float(55, "RSI long threshold")
+ rsiShortLevel = input.float(45, "RSI short threshold")
+ volumeLen = input.int(20, "Volume average", minval=1)
+ volumeMultiplier = input.float(1, "Volume multiplier", minval=0.1, step=0.05)
+ htf = input.timeframe("240", "Higher timeframe")
+ atrLen = input.int(14, "ATR length", minval=1)
+ atrMultiple = input.float(2, "ATR stop multiple", minval=0.1)
+ riskReward = input.float(2, "Risk/reward", minval=0.1)
+
+ // === Core calculations ===
+ emaFast = ta.ema(close, emaFastLen)
+ emaSlow = ta.ema(close, emaSlowLen)
+ longMa = ta.sma(close, longMaLen)
+ vwapValue = ta.vwap(hlc3)
+ rsiValue = ta.rsi(close, rsiLen)
+ volumeAverage = ta.sma(volume, volumeLen)
+ atrValue = ta.atr(atrLen)
+
+ // Uses the previous confirmed higher-timeframe candle with lookahead_on to avoid future leakage.
+ htfBull = request.security(syminfo.tickerid, htf, close[1] > ta.ema(close, 100)[1], lookahead=barmerge.lookahead_on)
+ htfBear = not htfBull
+
+ // === Filters and triggers ===
+ confirmationOk = not confirmedOnly or barstate.isconfirmed
+ chartTimeframeAliasOk = (timeframe.period == "1D" and expectedChartTimeframe == "D") or (timeframe.period == "D" and expectedChartTimeframe == "1D")
+ chartTimeframeOk = timeframe.period == expectedChartTimeframe or chartTimeframeAliasOk
+ chartTimeframeAllowed = not enforceChartTimeframe or chartTimeframeOk
+ longSetup = emaFast > emaSlow and close > longMa and close > vwapValue and rsiValue >= rsiLongLevel and volume >= volumeAverage * volumeMultiplier and htfBull and confirmationOk
+ shortSetup = emaFast < emaSlow and close < longMa and close < vwapValue and rsiValue <= rsiShortLevel and volume >= volumeAverage * volumeMultiplier and htfBear and confirmationOk
+ longTrigger = ta.crossover(close, emaFast)
+ shortTrigger = ta.crossunder(close, emaFast)
+ var int lastSignalBar = na
+ cooldownOk = na(lastSignalBar) or bar_index - lastSignalBar > cooldownBars
+ longSignal = chartTimeframeAllowed and longSetup and longTrigger and cooldownOk
+ shortSignal = chartTimeframeAllowed and shortSetup and shortTrigger and cooldownOk
+ if longSignal or shortSignal
+ lastSignalBar := bar_index
+
+ // === Visuals ===
+ barcolor(colorSignalBars and visualProfile != "Clean" ? longSetup ? (visualProfile == "Advanced" ? color.new(color.rgb(0, 165, 90), 15) : color.new(color.lime, 60)) : shortSetup ? (visualProfile == "Advanced" ? color.new(color.rgb(220, 50, 60), 15) : color.new(color.red, 60)) : na : na, title="Setup bar color")
+ bgcolor(showTrendRibbon and visualProfile != "Clean" ? (htfBull ? color.new(color.lime, 95) : color.new(color.red, 95)) : na, title="Trend ribbon", force_overlay=true)
+ plot(emaFast, "Fast EMA", color=color.aqua, force_overlay=true)
+ plot(emaSlow, "Slow EMA", color=color.orange, force_overlay=true)
+ plot(longMa, "Long MA", color=color.yel
|
|
tests/compiler.test.ts > dead-option protection > changes generated output when each major feature is toggled:
lib/compiler-v14.ts#L69
Error: Compiler transform anchor missing: alerts section
❯ compilePine lib/compiler-v14.ts:69:11
❯ compilePine lib/compiler-v15.ts:5:14
❯ compilePine lib/compiler-v16.ts:5:14
❯ compilePine lib/compiler-v17.ts:11:14
❯ compilePine lib/compiler-v18.ts:5:14
❯ compilePine lib/compiler-v19.ts:5:14
❯ compilePine lib/compiler-v20.ts:23:14
❯ compilePine lib/compiler.ts:10:10
❯ tests/compiler.test.ts:198:14
|
|
test-and-build
Node.js 20 is deprecated. The following actions target Node.js 20 but are being forced to run on Node.js 24: actions/checkout@v4, actions/setup-node@v4. For more information see: https://github.blog/changelog/2025-09-19-deprecation-of-node-20-on-github-actions-runners/
|