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
- Pine Script Formatting and Style Guidelines
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_countto 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
- Write code in small, testable chunks
- Compile frequently to catch errors early
- Test with different timeframes and symbols
- Use version control to track changes
- 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.