import { useState, useEffect } from 'react'; import { pinescriptAPI } from '../api/client'; import { formatCurrency, detectCurrencyFromTicker } from '../utils/currencyUtils'; import TickerSearch from '../components/TickerSearch'; /* ── SVG Icons ─────────────────────────────────────────────────────────── */ const CodeIcon = () => ; const TemplateIcon = () => ; const PlayIcon = () => ; const CopyIcon = () => ; const DownloadIcon = () => ; const CheckIcon = () => ; const XIcon = () => ; const CATEGORY_COLORS: Record = { 'Momentum': '#3b82f6', 'Mean Reversion': '#a855f7', 'Volatility': '#f59e0b', 'Trend Following': '#10b981', 'Oscillator': '#ec4899', 'Statistical': '#6366f1', 'Risk-Managed': '#14b8a6', 'Advanced': '#f97316', 'Intraday': '#06b6d4', 'Multi-Signal': '#8b5cf6', 'Hedging': '#ef4444', 'Forex': '#22d3ee', 'Crypto': '#d97706', 'Commodities': '#a3e635', 'Futures': '#c084fc', }; export default function PineScriptLab() { const [mode, setMode] = useState<'generate' | 'templates'>('templates'); const [description, setDescription] = useState(''); const [code, setCode] = useState(''); const [templates, setTemplates] = useState([]); const [backtestTicker, setBacktestTicker] = useState(''); const [backtestPeriod, setBacktestPeriod] = useState('3y'); const [loading, setLoading] = useState(false); const [generating, setGenerating] = useState(false); const [results, setResults] = useState(null); const [validation, setValidation] = useState(null); const [error, setError] = useState(''); const [copied, setCopied] = useState(false); useEffect(() => { loadTemplates(); }, []); const loadTemplates = async () => { try { const { data } = await pinescriptAPI.templates(); setTemplates(data.templates || []); } catch (err) { console.error('Failed to load templates'); } }; const handleGenerate = async () => { if (!description.trim()) return; setGenerating(true); setError(''); try { const { data } = await pinescriptAPI.generate({ description }); setCode(data.code || ''); setValidation(null); setResults(null); } catch (err: any) { setError(err.response?.data?.detail || 'Generation failed'); } finally { setGenerating(false); } }; const handleTemplateSelect = async (templateId: string) => { setGenerating(true); setError(''); try { const { data } = await pinescriptAPI.generateFromTemplate({ template_id: templateId }); setCode(data.code || ''); setValidation(null); setResults(null); } catch (err: any) { setError(err.response?.data?.detail || 'Template load failed'); } finally { setGenerating(false); } }; const handleValidate = async () => { if (!code.trim()) return; try { const { data } = await pinescriptAPI.validate({ code }); setValidation(data); } catch (err) { setError('Validation failed'); } }; const handleBacktest = async () => { if (!code.trim()) return; setLoading(true); setError(''); try { const { data } = await pinescriptAPI.backtest({ code, ticker: backtestTicker, period: backtestPeriod, }); setResults(data); } catch (err: any) { setError(err.response?.data?.detail || 'Backtest failed'); } finally { setLoading(false); } }; const handleCopy = () => { navigator.clipboard.writeText(code); setCopied(true); setTimeout(() => setCopied(false), 2000); }; const handleDownload = () => { const blob = new Blob([code], { type: 'text/plain' }); const url = URL.createObjectURL(blob); const a = document.createElement('a'); a.href = url; a.download = 'strategy.pine'; a.click(); URL.revokeObjectURL(url); }; const metricColor = (val: number, positive: boolean = true) => { if (positive) return val >= 0 ? 'var(--accent-green)' : '#ef4444'; return val >= 0 ? '#ef4444' : 'var(--accent-green)'; }; return (
{/* Header */}

Pine Script Lab

Generate, validate, and backtest TradingView Pine Script v5 strategies

{/* ── Left Panel: Code Generation ────────────────────────────── */}
{/* Mode Toggle */}
{/* Template Browser */} {mode === 'templates' && (
{templates.map((t: any) => ( ))}
)} {/* NL Generator */} {mode === 'generate' && (