Skip to main content

Pine Script Formatting and Style Guidelines

Overview

This document provides comprehensive formatting and style guidelines for Pine Script development, based on real-world refactoring experience and Pine Script compiler requirements. These guidelines ensure code maintainability, readability, and compilation success.

Table of Contents

Indentation and Spacing

Function Definitions

  • Use 4 spaces for indentation inside function bodies
  • Function parameters should be aligned consistently
  • Multi-line function signatures should have proper indentation
// ✅ Correct
f_checkDivergenceConditions(MACDWave wave0, MACDWave wave1, MACDWave wave2, bool isBull) =>
cond0 = not i_alertMACDDivergence0TF or (isBull ? wave0.bullDiv : wave0.bearDiv)
cond1 = not i_alertMACDDivergence1TF or (isBull ? wave1.bullDiv : wave1.bearDiv)
cond2 = not i_alertMACDDivergence2TF or (isBull ? wave2.bullDiv : wave2.bearDiv)
cond0 and cond1 and cond2

// ❌ Incorrect - inconsistent indentation
f_checkDivergenceConditions(MACDWave wave0, MACDWave wave1, MACDWave wave2, bool isBull) =>
cond0 = not i_alertMACDDivergence0TF or (isBull ? wave0.bullDiv : wave0.bearDiv)
cond1 = not i_alertMACDDivergence1TF or (isBull ? wave1.bullDiv : wave1.bearDiv)
cond2 = not i_alertMACDDivergence2TF or (isBull ? wave2.bullDiv : wave2.bearDiv)
cond0 and cond1 and cond2

Multi-line Function Calls

  • When function calls span multiple lines, align parameters properly
  • Use consistent indentation for continuation lines
// ✅ Correct
alertBull = f_checkDivergenceConditions(Wave0, HTF1Wave0, HTF2Wave0, true) and
f_checkRSIConditions(rsiVal, rsiHTF1Val, rsiHTF2Val, true)

// ❌ Incorrect - improper alignment
alertBull = f_checkDivergenceConditions(Wave0, HTF1Wave0, HTF2Wave0, true) and
f_checkRSIConditions(rsiVal, rsiHTF1Val, rsiHTF2Val, true)

Boolean Expressions

  • Multi-line boolean expressions should be properly aligned
  • Use consistent indentation for logical operators
// ✅ Correct
alertBear = f_checkDivergenceConditions(Wave0, HTF1Wave0, HTF2Wave0, false) and
f_checkRSIConditions(rsiVal, rsiHTF1Val, rsiHTF2Val, false)

// ❌ Incorrect - misaligned continuation
alertBear = f_checkDivergenceConditions(Wave0, HTF1Wave0, HTF2Wave0, false) and
f_checkRSIConditions(rsiVal, rsiHTF1Val, rsiHTF2Val, false)

Conditional Statements

  • Use proper indentation for if/else blocks
  • Maintain consistent spacing around operators
// ✅ Correct
if sendAlert
totalSignals += 1
msg = f_generateAlertMessage(alertBull, Wave0, HTF1Wave0, HTF2Wave0, rsiVal, rsiHTF1Val, rsiHTF2Val)

if i_showHistoricalAlerts
label.new(time, alertBull ? low : high, alertBull ? 'Buy' : 'Sell', xloc.bar_time,
color=alertBull ? C_BULLISH1 : C_BEARISH1, textcolor=C_NEUTRAL,
style=alertBull ? label.style_label_up : label.style_label_down,
tooltip=msg, force_overlay=true)
alert(msg)

Variable Assignments

  • Use consistent spacing around assignment operators
  • Align related assignments when appropriate
// ✅ Correct
cond0 = not i_alertMACDDivergence0TF or (isBull ? wave0.bullDiv : wave0.bearDiv)
cond1 = not i_alertMACDDivergence1TF or (isBull ? wave1.bullDiv : wave1.bearDiv)
cond2 = not i_alertMACDDivergence2TF or (isBull ? wave2.bullDiv : wave2.bearDiv)

Code Organization

Section Headers

  • Use clear section dividers with consistent formatting
  • Include descriptive comments for major sections
// =====================================================
// SECTION NAME
// =====================================================

Constants

  • Group related constants together
  • Use descriptive names with appropriate prefixes
  • Organize by functional area
// MACD Configuration
MACD_FAST_DEFAULT = 6
MACD_SLOW_DEFAULT = 13
MACD_FACTOR_DEFAULT = 2.0
MACD_FACTOR_MIN = 1.0
MACD_FACTOR_MAX = 20.0

// RSI Configuration
RSI_OVERBOUGHT_L1 = 70
RSI_OVERBOUGHT_L2 = 80
RSI_OVERBOUGHT_L3 = 90
RSI_OVERSOLD_L1 = 30
RSI_OVERSOLD_L2 = 20
RSI_OVERSOLD_L3 = 10

Function Documentation

  • Use JSDoc-style comments for function documentation
  • Include parameter descriptions and return value information
//@function Check divergence conditions for alert
//@param wave0 Current timeframe wave
//@param wave1 Higher timeframe 1 wave
//@param wave2 Higher timeframe 2 wave
//@param isBull Whether checking for bullish divergence
//@returns Boolean indicating if divergence conditions are met
f_checkDivergenceConditions(MACDWave wave0, MACDWave wave1, MACDWave wave2, bool isBull) =>
// Function body...

Best Practices

Code Modularity

  • Break complex logic into smaller, focused functions
  • Use helper functions to reduce code duplication
  • Separate configuration from business logic
  • Group related functionality together
// ✅ Good - modular approach
f_calculateIndicators() =>
macdData = f_calculateMACD(close, i_macdFast, i_macdSlow, i_macdSignal)
rsiData = f_calculateRSI(close, i_rsiPeriod)
[macdData, rsiData]

f_checkAlertConditions(macdData, rsiData) =>
divergenceCondition = f_hasDivergence(macdData)
rsiCondition = f_isRSIExtreme(rsiData)
divergenceCondition and rsiCondition

// ❌ Poor - monolithic approach
// All logic crammed into one large block

Code Reusability

  • Create utility functions for common operations
  • Use parameterized functions instead of duplicating code
  • Design functions to be reusable across different contexts
// ✅ Good - reusable utility function
f_formatNumber(float value, int decimals) =>
str.tostring(value, format.decimal, decimals)

// Usage in multiple contexts
rsiText = "RSI: " + f_formatNumber(rsiValue, 2)
macdText = "MACD: " + f_formatNumber(macdValue, 4)

// ❌ Poor - duplicated formatting logic
rsiText = "RSI: " + str.tostring(rsiValue, format.decimal, 2)
macdText = "MACD: " + str.tostring(macdValue, format.decimal, 4)

Documentation Standards

  • Document all user-defined functions with JSDoc-style comments
  • Include parameter descriptions and return value information
  • Add inline comments for complex logic
  • Maintain a changelog for version tracking
//@function Check if RSI indicates extreme conditions
//@param rsiValue Current RSI value (0-100)
//@param isOverboughtCheck True to check overbought, false for oversold
//@param threshold1 Primary threshold level
//@param threshold2 Secondary threshold level (optional)
//@returns Boolean indicating if extreme condition exists
f_isRSIExtreme(float rsiValue, bool isOverboughtCheck, float threshold1, float threshold2 = na) =>
if na(rsiValue)
false
else if isOverboughtCheck
rsiValue > threshold1 or (not na(threshold2) and rsiValue > threshold2)
else
rsiValue < threshold1 or (not na(threshold2) and rsiValue < threshold2)

Testing and Validation

  • Test indicators with different timeframes
  • Verify calculations with known values
  • Check edge cases (NA values, zero values, extreme values)
  • Test alert functionality thoroughly
// Example of defensive coding with validation
f_calculatePercentChange(float current, float previous) =>
// Validate inputs
if na(current) or na(previous)
na
else if previous == 0
current > 0 ? 100.0 : current < 0 ? -100.0 : 0.0
else
(current - previous) / math.abs(previous) * 100

Naming Conventions

Variable Naming

  • Use camelCase for variables: currentPrice, movingAverage, isUptrend
  • Use descriptive names that explain the variable's purpose
  • Avoid abbreviations unless they are well-known (RSI, MACD, EMA)
// ✅ Good
currentClose = close
exponentialMA = ta.ema(close, 20)
isDivergenceBullish = macdValue > 0 and priceValue < 0

// ❌ Poor
c = close
ema20 = ta.ema(close, 20)
div = macdValue > 0 and priceValue < 0

Function Naming

  • Use f_ prefix for user-defined functions
  • Use descriptive verbs that explain what the function does
  • Use camelCase after the prefix
// ✅ Good
f_calculateMACD(fast, slow, signal) => ...
f_validateInput(value, min, max) => ...
f_generateAlertMessage(condition, data) => ...

// ❌ Poor
calc(f, s, sig) => ...
check(v, mn, mx) => ...
msg(c, d) => ...

Input Variables

  • Use i_ prefix for input variables
  • Use descriptive names that match the input label
// ✅ Good
i_macdFast = input.int(12, "MACD Fast Length")
i_showTable = input.bool(true, "Show Information Table")
i_alertEnabled = input.bool(false, "Enable Alerts")

// ❌ Poor
fast = input.int(12, "MACD Fast Length")
tbl = input.bool(true, "Show Information Table")
alert = input.bool(false, "Enable Alerts")

Constants

  • Use ALL_CAPS with underscores for constants
  • Group related constants with common prefixes
  • Include DEFAULT, MIN, MAX variants where applicable
// ✅ Good
MACD_FAST_DEFAULT = 12
MACD_SLOW_DEFAULT = 26
MACD_SIGNAL_DEFAULT = 9
MACD_FAST_MIN = 1
MACD_FAST_MAX = 50

RSI_OVERBOUGHT_L1 = 70
RSI_OVERBOUGHT_L2 = 80
RSI_OVERSOLD_L1 = 30
RSI_OVERSOLD_L2 = 20

// ❌ Poor
fast = 12
slow = 26
ob1 = 70
os1 = 30

Color Constants

  • Use C_ prefix for color constants
  • Use descriptive color names that indicate usage
// ✅ Good
C_BULLISH1 = color.new(color.green, 0)
C_BEARISH1 = color.new(color.red, 0)
C_NEUTRAL = color.new(color.gray, 0)
C_TABLE_BG = color.new(color.white, 80)

// ❌ Poor
green1 = color.new(color.green, 0)
red1 = color.new(color.red, 0)
gray = color.new(color.gray, 0)

Function Design

Function Structure

  • Keep functions focused on a single responsibility
  • Use proper parameter typing
  • Include comprehensive documentation
//@function Calculate and validate exponential moving average
//@param source The price series to calculate EMA for
//@param length The number of periods for the EMA
//@returns Validated EMA value, NA if inputs are invalid
f_calculateEMA(series float source, simple int length) =>
// Input validation
if na(source) or length <= 0
na
else
ta.ema(source, length)

Parameter Order

  • Place most important parameters first
  • Group related parameters together
  • Use consistent parameter ordering across similar functions
// ✅ Good - consistent parameter ordering
f_calculateMACD(series float source, simple int fast, simple int slow, simple int signal) => ...
f_calculateStochastic(series float high, series float low, series float close, simple int k, simple int d) => ...

// ❌ Poor - inconsistent ordering
f_calculateMACD(simple int fast, series float source, simple int signal, simple int slow) => ...

Return Values

  • Be consistent with return types
  • Document what NA returns mean
  • Use meaningful return values
//@function Validate numeric input within bounds
//@param value Input value to validate
//@param minVal Minimum allowed value
//@param maxVal Maximum allowed value
//@param defaultVal Default value if input is invalid
//@returns Clamped value within bounds, or default if NA
f_validateNumericInput(float value, float minVal, float maxVal, float defaultVal) =>
if na(value)
defaultVal
else
math.max(minVal, math.min(maxVal, value))

Constants and Configuration

Grouping Constants

  • Group related constants by functionality
  • Use consistent naming patterns within groups
  • Add comments to explain non-obvious values
// MACD Configuration
MACD_FAST_DEFAULT = 12 // Standard fast EMA period
MACD_SLOW_DEFAULT = 26 // Standard slow EMA period
MACD_SIGNAL_DEFAULT = 9 // Signal line EMA period
MACD_FACTOR_DEFAULT = 2.0 // Divergence detection factor
MACD_FACTOR_MIN = 1.0 // Minimum allowed factor
MACD_FACTOR_MAX = 20.0 // Maximum allowed factor

// Display Limits (Pine Script constraints)
MAX_LABELS = 500 // Maximum labels allowed
MAX_LINES = 500 // Maximum lines allowed
MAX_BOXES = 500 // Maximum boxes allowed

// Table Configuration
TABLE_ROWS_MAX = 20 // Maximum table rows
TABLE_COLS_MAX = 10 // Maximum table columns
TABLE_CELL_WIDTH = 100 // Default cell width in pixels

Magic Number Prevention

  • Replace all magic numbers with named constants
  • Provide context for non-obvious values
  • Make configuration values easily changeable
// ✅ Good - named constants with context
RSI_PERIOD_DEFAULT = 14 // Standard RSI calculation period
RSI_OVERBOUGHT_THRESHOLD = 70 // Traditional overbought level
DIVERGENCE_LOOKBACK = 60 // Bars to look back for divergence

if rsi > RSI_OVERBOUGHT_THRESHOLD
// Handle overbought condition

// ❌ Poor - magic numbers
if rsi > 70 // What does 70 represent?
// Handle condition

Error Handling

Input Validation

  • Validate all user inputs
  • Provide sensible defaults
  • Handle edge cases gracefully
//@function Comprehensive input validation with error handling
f_validateInputs() =>
// Validate MACD parameters
macdFast := f_validateNumericInput(i_macdFast, MACD_FAST_MIN, MACD_FAST_MAX, MACD_FAST_DEFAULT)
macdSlow := f_validateNumericInput(i_macdSlow, MACD_SLOW_MIN, MACD_SLOW_MAX, MACD_SLOW_DEFAULT)

// Ensure fast < slow for MACD
if macdFast >= macdSlow
macdFast := MACD_FAST_DEFAULT
macdSlow := MACD_SLOW_DEFAULT

// Validate RSI period
rsiPeriod := f_validateNumericInput(i_rsiPeriod, RSI_PERIOD_MIN, RSI_PERIOD_MAX, RSI_PERIOD_DEFAULT)

[macdFast, macdSlow, rsiPeriod]

Safe Calculations

  • Handle division by zero
  • Check for NA values before calculations
  • Use defensive programming practices
//@function Safe division with zero check
f_safeDivision(float numerator, float denominator, float defaultValue) =>
if na(numerator) or na(denominator) or denominator == 0
defaultValue
else
numerator / denominator

//@function Safe percentage calculation
f_safePercentage(float current, float previous) =>
if na(current) or na(previous) or previous == 0
0.0
else
(current - previous) / previous * 100

Performance Considerations

Limiting Calculations

  • Use calc_bars_count to limit historical calculations
  • Implement early returns where possible
  • Cache expensive calculations
// Limit calculations to recent bars
MAX_CALC_BARS = 1000
calcBarsCount = math.min(bar_index + 1, MAX_CALC_BARS)

// Only calculate if within limits
if bar_index >= calcBarsCount
// Perform calculations
macdLine := ta.ema(close, macdFast) - ta.ema(close, macdSlow)
signalLine := ta.ema(macdLine, macdSignal)

Efficient Data Structures

  • Use appropriate data types
  • Minimize security() calls
  • Group related calculations
// ✅ Good - group related security calls
[htfClose, htfHigh, htfLow] = request.security(syminfo.tickerid, htfTimeframe, [close, high, low])

// ❌ Poor - separate security calls
htfClose = request.security(syminfo.tickerid, htfTimeframe, close)
htfHigh = request.security(syminfo.tickerid, htfTimeframe, high)
htfLow = request.security(syminfo.tickerid, htfTimeframe, low)

Common Compilation Errors to Avoid

1. Indentation Issues

Pine Script is very sensitive to indentation. Always use 4 spaces consistently.

// ❌ Incorrect - mixed indentation
f_example() =>
if condition
value = 1 // Wrong: 6 spaces
else
value = 2 // Wrong: 2 spaces
value

// ✅ Correct - consistent 4-space indentation
f_example() =>
if condition
value = 1
else
value = 2
value

2. Multi-line Expression Alignment

Continuation lines must be properly aligned to avoid compilation errors.

// ❌ Incorrect - improper alignment
result = longFunction(param1, param2,
param3, param4) // Wrong: not aligned

// ✅ Correct - proper alignment
result = longFunction(param1, param2,
param3, param4)

3. Forward Reference Errors

Define functions before they are used, or ensure proper ordering.

// ❌ Incorrect - function used before definition
value = f_calculate(10) // Error: f_calculate not yet defined

f_calculate(x) => x * 2

// ✅ Correct - function defined before use
f_calculate(x) => x * 2

value = f_calculate(10)

4. Missing Spaces Around Operators

Pine Script requires spaces around most operators.

// ❌ Incorrect - missing spaces
result=value1+value2*factor // Compilation error

// ✅ Correct - proper spacing
result = value1 + value2 * factor

5. Inconsistent Boolean Expression Formatting

Multi-line boolean expressions need consistent formatting.

// ❌ Incorrect - misaligned boolean operators
condition = value1 > threshold and
value2 < limit or // Wrong alignment
value3 == target

// ✅ Correct - properly aligned
condition = value1 > threshold and
value2 < limit or
value3 == target

6. Type Declaration Errors

Ensure proper type declarations and consistency.

// ❌ Incorrect - inconsistent types
f_process(value) => // No type specified
simple int result = value // Type mismatch

// ✅ Correct - consistent typing
f_process(simple int value) =>
result = value * 2
result

7. Scope and Variable Declaration Issues

Be careful with variable scope and declarations.

// ❌ Incorrect - variable scope issues
if condition
localVar = 10
result = localVar // Error: localVar not in scope

// ✅ Correct - proper variable scope
result = if condition
10
else
0

Tools and Validation

Editor Configuration

  • Set your editor to use 4 spaces for indentation
  • Enable visible whitespace to catch indentation errors
  • Use consistent line endings (LF recommended)
  • Enable syntax highlighting for Pine Script

Development Workflow

  1. Write code in small, testable chunks
  2. Compile frequently to catch errors early
  3. Test with different timeframes and symbols
  4. Use version control to track changes
  5. Document changes in changelog

Debugging Tips

  • Use runtime.error() for debugging complex logic
  • Add temporary plots to visualize intermediate calculations
  • Use str.format() for detailed debug messages
  • Test edge cases with historical data

Examples of Well-Formatted Code

Complete Function Example

//@function Calculate MACD with comprehensive validation and error handling
//@param source Price series for calculation (typically close)
//@param fastLength Fast EMA period (must be > 0 and < slowLength)
//@param slowLength Slow EMA period (must be > fastLength)
//@param signalLength Signal line EMA period (must be > 0)
//@returns Tuple containing [macdLine, signalLine, histogram] or [na, na, na] if invalid
f_calculateMACDWithValidation(series float source, simple int fastLength, simple int slowLength, simple int signalLength) =>
// Input validation
if na(source) or fastLength <= 0 or slowLength <= 0 or signalLength <= 0 or fastLength >= slowLength
[na, na, na]
else
// Calculate MACD components
fastEMA = ta.ema(source, fastLength)
slowEMA = ta.ema(source, slowLength)
macdLine = fastEMA - slowEMA
signalLine = ta.ema(macdLine, signalLength)
histogram = macdLine - signalLine

[macdLine, signalLine, histogram]

Table Creation Example

//@function Create and populate information table with proper formatting
//@returns Table object with current indicator values
f_createInfoTable() =>
// Table configuration
tablePosition = i_tablePosition
tableBgColor = f_getThemeColor("tableBg")
tableTextColor = f_getThemeColor("tableText")

// Create table
infoTable = table.new(position=tablePosition,
columns=TABLE_COLS_MAX,
rows=TABLE_ROWS_MAX,
bgcolor=tableBgColor,
border_width=1)

// Populate table with current values
table.cell(table_id=infoTable, column=0, row=0,
text="MACD", text_color=tableTextColor, text_size=size.small)
table.cell(table_id=infoTable, column=1, row=0,
text=f_formatNumber(macdValue, 4), text_color=tableTextColor, text_size=size.small)

table.cell(table_id=infoTable, column=0, row=1,
text="RSI", text_color=tableTextColor, text_size=size.small)
table.cell(table_id=infoTable, column=1, row=1,
text=f_formatNumber(rsiValue, 2), text_color=tableTextColor, text_size=size.small)

infoTable

Alert Logic Example

//@function Generate comprehensive alert message with proper formatting
//@param isBullish Whether the alert is for bullish or bearish condition
//@param macdData Current MACD values
//@param rsiData Current RSI values
//@param timeframe Current timeframe
//@returns Formatted alert message string
f_generateAlertMessage(bool isBullish, MACDData macdData, RSIData rsiData, string timeframe) =>
// Create base message
direction = isBullish ? "BULLISH" : "BEARISH"
signal = isBullish ? "BUY" : "SELL"

// Format values
macdText = f_formatNumber(macdData.line, 4)
rsiText = f_formatNumber(rsiData.value, 2)

// Construct detailed message
message = str.format("{0} SIGNAL DETECTED\n" +
"Symbol: {1}\n" +
"Timeframe: {2}\n" +
"MACD: {3}\n" +
"RSI: {4}\n" +
"Action: {5}",
direction,
syminfo.ticker,
timeframe,
macdText,
rsiText,
signal)

message

Changelog

  • v2.0.0 (2025-01-28): Major expansion with comprehensive guidelines

    • Added detailed naming conventions section
    • Included function design principles and best practices
    • Added constants and configuration management guidelines
    • Included comprehensive error handling strategies
    • Added performance considerations and optimization tips
    • Expanded common compilation errors with specific examples
    • Added tools, validation, and debugging sections
    • Included complete, well-formatted code examples
    • Restructured document with table of contents for better navigation
  • v1.0.0 (2025-01-28): Initial guidelines based on MACDRSI+ indicator refactoring

    • Established indentation rules
    • Documented multi-line expression formatting
    • Added function documentation standards
    • Included common error patterns to avoid

These comprehensive guidelines are based on extensive Pine Script development experience and real-world refactoring projects. Following these patterns will result in maintainable, error-free, and professional Pine Script code. Always test your code thoroughly after applying formatting changes and maintain consistency across your entire codebase.