jashdoshi77's picture
whole lotta changes
e6021a3
"""
Pine Script v5 Syntax Validator.
Validates generated Pine Script code for:
- Version declaration
- Required structure (strategy or indicator)
- Bracket/parenthesis matching
- Known function signatures
- Common mistakes and deprecated syntax
"""
from __future__ import annotations
import logging
import re
from dataclasses import dataclass
from typing import Any, Dict, List
logger = logging.getLogger(__name__)
@dataclass
class ValidationResult:
valid: bool
errors: List[Dict[str, Any]]
warnings: List[Dict[str, Any]]
stats: Dict[str, int]
# Pine Script v5 built-in functions / namespaces
VALID_NAMESPACES = {
"ta", "math", "strategy", "input", "color", "plot", "hline",
"plotshape", "plotchar", "fill", "bgcolor", "barcolor",
"request", "str", "array", "matrix", "table", "line", "box",
"label", "syminfo", "time", "timeframe", "chart",
}
VALID_TA_FUNCTIONS = {
"sma", "ema", "wma", "rma", "vwma", "swma", "alma",
"rsi", "macd", "stoch", "cci", "atr", "tr",
"highest", "lowest", "highestbars", "lowestbars",
"crossover", "crossunder", "cross",
"change", "mom", "roc", "percentrank",
"supertrend", "pivot_point_levels",
"bb", "kc", "vwap",
"stdev", "variance", "correlation",
"barssince", "valuewhen",
"cum", "rising", "falling",
"dmi", "mfi", "obv",
}
DEPRECATED_V4 = {
"study": "indicator",
"security": "request.security",
"input": "input.int/input.float/input.bool/input.string",
"iff": "condition ? value1 : value2",
}
def validate_pine_script(code: str) -> Dict[str, Any]:
"""
Validate Pine Script v5 code and return errors/warnings.
"""
errors: List[Dict[str, Any]] = []
warnings: List[Dict[str, Any]] = []
lines = code.strip().split("\n")
stats = {
"total_lines": len(lines),
"code_lines": 0,
"comment_lines": 0,
}
if not code.strip():
errors.append({"line": 0, "message": "Empty code"})
return ValidationResult(
valid=False, errors=errors, warnings=warnings, stats=stats
).__dict__
# 1. Version declaration check
has_version = False
for i, line in enumerate(lines):
stripped = line.strip()
if stripped.startswith("//"):
stats["comment_lines"] += 1
if stripped.startswith("//@version=5"):
has_version = True
elif stripped:
stats["code_lines"] += 1
if not has_version:
errors.append({
"line": 1,
"message": "Missing Pine Script v5 version declaration: //@version=5",
"severity": "error",
})
# 2. Strategy or indicator declaration
has_strategy = bool(re.search(r'\bstrategy\s*\(', code))
has_indicator = bool(re.search(r'\bindicator\s*\(', code))
if not has_strategy and not has_indicator:
errors.append({
"line": 0,
"message": "Missing strategy() or indicator() declaration",
"severity": "error",
})
# 3. Bracket matching
open_parens = 0
open_brackets = 0
for i, line in enumerate(lines, 1):
stripped = line.strip()
if stripped.startswith("//"):
continue
open_parens += stripped.count("(") - stripped.count(")")
open_brackets += stripped.count("[") - stripped.count("]")
if open_parens != 0:
errors.append({
"line": 0,
"message": f"Unmatched parentheses: {'extra (' if open_parens > 0 else 'extra )'}",
"severity": "error",
})
if open_brackets != 0:
errors.append({
"line": 0,
"message": f"Unmatched brackets: {'extra [' if open_brackets > 0 else 'extra ]'}",
"severity": "error",
})
# 4. Deprecated v4 syntax
for deprecated, replacement in DEPRECATED_V4.items():
pattern = rf'\b{deprecated}\s*\('
match = re.search(pattern, code)
if match:
# Find line number
pos = match.start()
line_num = code[:pos].count("\n") + 1
# study() is a special case — it was renamed to indicator()
if deprecated == "study":
warnings.append({
"line": line_num,
"message": f"Deprecated: '{deprecated}()' was renamed to '{replacement}()' in v5",
"severity": "warning",
})
elif deprecated == "security":
warnings.append({
"line": line_num,
"message": f"Deprecated: '{deprecated}()' should be '{replacement}()' in v5",
"severity": "warning",
})
# 5. Strategy without entry
if has_strategy:
if "strategy.entry" not in code:
warnings.append({
"line": 0,
"message": "Strategy has no strategy.entry() calls — no trades will be executed",
"severity": "warning",
})
# 6. Check for common mistakes
if "var " in code:
# var is valid but sometimes misused
pass
# Missing quotes around strings
name_match = re.search(r'strategy\(\s*([^"\'])', code)
if name_match:
pos = name_match.start()
line_num = code[:pos].count("\n") + 1
warnings.append({
"line": line_num,
"message": "Strategy name should be a quoted string",
"severity": "warning",
})
is_valid = len(errors) == 0
return {
"valid": is_valid,
"errors": [{"line": e["line"], "message": e["message"], "severity": e.get("severity", "error")} for e in errors],
"warnings": [{"line": w["line"], "message": w["message"], "severity": w.get("severity", "warning")} for w in warnings],
"stats": stats,
}