""" Pine Script v5 Strategy Generator. Generates production-ready TradingView Pine Script v5 code from: 1. Natural language descriptions (via LLM) 2. Pre-built strategy templates 3. Custom parameter configurations Supports 12+ built-in templates covering all strategy types: - Trend following, mean reversion, momentum, breakout - Multi-timeframe, oscillator, volatility-based """ from __future__ import annotations import logging from typing import Any, Dict, List, Optional import aiohttp from app.config import get_settings logger = logging.getLogger(__name__) _settings = get_settings() # ── Strategy Templates ─────────────────────────────────────────────────── STRATEGY_TEMPLATES: Dict[str, Dict[str, Any]] = { "sma_crossover": { "id": "sma_crossover", "name": "SMA Crossover", "category": "Momentum", "description": "Classic dual moving average crossover strategy. Buys on golden cross, sells on death cross.", "parameters": {"fast_length": 20, "slow_length": 50}, "code": ''' //@version=5 strategy("SMA Crossover Strategy", overlay=true, default_qty_type=strategy.percent_of_equity, default_qty_value=100, commission_type=strategy.commission.percent, commission_value=0.1) // Inputs fast_length = input.int({fast_length}, title="Fast SMA Length", minval=1) slow_length = input.int({slow_length}, title="Slow SMA Length", minval=1) use_stop_loss = input.bool(true, title="Use Stop Loss") stop_loss_pct = input.float(5.0, title="Stop Loss %", minval=0.1, step=0.1) use_take_profit = input.bool(true, title="Use Take Profit") take_profit_pct = input.float(10.0, title="Take Profit %", minval=0.1, step=0.1) // Calculations fast_sma = ta.sma(close, fast_length) slow_sma = ta.sma(close, slow_length) // Conditions long_condition = ta.crossover(fast_sma, slow_sma) short_condition = ta.crossunder(fast_sma, slow_sma) // Strategy execution if long_condition strategy.entry("Long", strategy.long) if short_condition strategy.close("Long") // Risk management if use_stop_loss or use_take_profit strategy.exit("Exit", "Long", stop=use_stop_loss ? strategy.position_avg_price * (1 - stop_loss_pct / 100) : na, limit=use_take_profit ? strategy.position_avg_price * (1 + take_profit_pct / 100) : na) // Plotting plot(fast_sma, color=color.new(color.blue, 0), title="Fast SMA", linewidth=2) plot(slow_sma, color=color.new(color.red, 0), title="Slow SMA", linewidth=2) bgcolor(strategy.position_size > 0 ? color.new(color.green, 90) : na) ''', }, "rsi_reversal": { "id": "rsi_reversal", "name": "RSI Mean Reversion", "category": "Mean Reversion", "description": "Buys when RSI is oversold, sells when overbought. Classic counter-trend strategy.", "parameters": {"rsi_length": 14, "oversold": 30, "overbought": 70}, "code": ''' //@version=5 strategy("RSI Mean Reversion", overlay=true, default_qty_type=strategy.percent_of_equity, default_qty_value=100, commission_type=strategy.commission.percent, commission_value=0.1) // Inputs rsi_length = input.int({rsi_length}, title="RSI Length", minval=2) oversold_level = input.int({oversold}, title="Oversold Level", minval=1, maxval=50) overbought_level = input.int({overbought}, title="Overbought Level", minval=50, maxval=99) stop_loss_pct = input.float(3.0, title="Stop Loss %", minval=0.1, step=0.1) take_profit_pct = input.float(6.0, title="Take Profit %", minval=0.1, step=0.1) // Calculations rsi_val = ta.rsi(close, rsi_length) // Conditions long_condition = ta.crossover(rsi_val, oversold_level) exit_condition = ta.crossunder(rsi_val, overbought_level) // Execution if long_condition strategy.entry("Long", strategy.long) if exit_condition strategy.close("Long") strategy.exit("SL/TP", "Long", stop=strategy.position_avg_price * (1 - stop_loss_pct / 100), limit=strategy.position_avg_price * (1 + take_profit_pct / 100)) // Plot hline(oversold_level, color=color.green, linestyle=hline.style_dashed) hline(overbought_level, color=color.red, linestyle=hline.style_dashed) ''', }, "macd_signal": { "id": "macd_signal", "name": "MACD Signal Line Crossover", "category": "Momentum", "description": "Buys on MACD bullish crossover, closes on bearish crossover with histogram confirmation.", "parameters": {"fast": 12, "slow": 26, "signal": 9}, "code": ''' //@version=5 strategy("MACD Strategy", overlay=true, default_qty_type=strategy.percent_of_equity, default_qty_value=100, commission_type=strategy.commission.percent, commission_value=0.1) fast_len = input.int({fast}, title="MACD Fast Length") slow_len = input.int({slow}, title="MACD Slow Length") sig_len = input.int({signal}, title="Signal Length") stop_loss_pct = input.float(4.0, title="Stop Loss %") take_profit_pct = input.float(8.0, title="Take Profit %") [macdLine, signalLine, hist] = ta.macd(close, fast_len, slow_len, sig_len) long_cond = ta.crossover(macdLine, signalLine) and hist > 0 exit_cond = ta.crossunder(macdLine, signalLine) if long_cond strategy.entry("Long", strategy.long) if exit_cond strategy.close("Long") strategy.exit("Exit", "Long", stop=strategy.position_avg_price * (1 - stop_loss_pct / 100), limit=strategy.position_avg_price * (1 + take_profit_pct / 100)) ''', }, "bollinger_breakout": { "id": "bollinger_breakout", "name": "Bollinger Band Breakout", "category": "Volatility", "description": "Buys when price breaks above upper band, sells on return to middle band. Captures volatility expansion.", "parameters": {"bb_length": 20, "bb_std": 2.0}, "code": ''' //@version=5 strategy("Bollinger Breakout", overlay=true, default_qty_type=strategy.percent_of_equity, default_qty_value=100, commission_type=strategy.commission.percent, commission_value=0.1) length = input.int({bb_length}, title="BB Length") mult = input.float({bb_std}, title="BB StdDev") stop_loss_pct = input.float(3.0, title="Stop Loss %") basis = ta.sma(close, length) upper = basis + mult * ta.stdev(close, length) lower = basis - mult * ta.stdev(close, length) long_cond = ta.crossover(close, upper) exit_cond = ta.crossunder(close, basis) if long_cond strategy.entry("Long", strategy.long) if exit_cond strategy.close("Long") strategy.exit("SL", "Long", stop=strategy.position_avg_price * (1 - stop_loss_pct / 100)) plot(basis, color=color.orange, title="Basis") plot(upper, color=color.blue, title="Upper") plot(lower, color=color.blue, title="Lower") ''', }, "supertrend": { "id": "supertrend", "name": "Supertrend", "category": "Trend Following", "description": "ATR-based trend following with dynamic support/resistance. Excellent for capturing strong trends.", "parameters": {"atr_length": 10, "factor": 3.0}, "code": ''' //@version=5 strategy("Supertrend Strategy", overlay=true, default_qty_type=strategy.percent_of_equity, default_qty_value=100, commission_type=strategy.commission.percent, commission_value=0.1) atr_len = input.int({atr_length}, title="ATR Length") factor = input.float({factor}, title="Factor") [supertrend, direction] = ta.supertrend(factor, atr_len) long_cond = ta.crossover(close, supertrend) short_cond = ta.crossunder(close, supertrend) if long_cond strategy.entry("Long", strategy.long) if short_cond strategy.close("Long") plot(supertrend, color=direction < 0 ? color.green : color.red, title="Supertrend", linewidth=2) ''', }, "ema_ribbon": { "id": "ema_ribbon", "name": "EMA Ribbon", "category": "Trend Following", "description": "Multiple EMA fan for trend confirmation. All EMAs aligned = strong trend signal.", "parameters": {"ema1": 8, "ema2": 13, "ema3": 21, "ema4": 34, "ema5": 55}, "code": ''' //@version=5 strategy("EMA Ribbon Strategy", overlay=true, default_qty_type=strategy.percent_of_equity, default_qty_value=100, commission_type=strategy.commission.percent, commission_value=0.1) e1 = input.int({ema1}, title="EMA 1") e2 = input.int({ema2}, title="EMA 2") e3 = input.int({ema3}, title="EMA 3") e4 = input.int({ema4}, title="EMA 4") e5 = input.int({ema5}, title="EMA 5") stop_loss_pct = input.float(4.0, title="Stop Loss %") ema1 = ta.ema(close, e1) ema2 = ta.ema(close, e2) ema3 = ta.ema(close, e3) ema4 = ta.ema(close, e4) ema5 = ta.ema(close, e5) bullish_ribbon = ema1 > ema2 and ema2 > ema3 and ema3 > ema4 and ema4 > ema5 bearish_ribbon = ema1 < ema2 and ema2 < ema3 and ema3 < ema4 and ema4 < ema5 if bullish_ribbon and not bullish_ribbon[1] strategy.entry("Long", strategy.long) if bearish_ribbon strategy.close("Long") strategy.exit("SL", "Long", stop=strategy.position_avg_price * (1 - stop_loss_pct / 100)) plot(ema1, color=color.new(#26A69A, 0), linewidth=1) plot(ema2, color=color.new(#2196F3, 0), linewidth=1) plot(ema3, color=color.new(#FF9800, 0), linewidth=1) plot(ema4, color=color.new(#E91E63, 0), linewidth=1) plot(ema5, color=color.new(#9C27B0, 0), linewidth=1) ''', }, "stochastic_rsi_combo": { "id": "stochastic_rsi_combo", "name": "Stochastic + RSI Combo", "category": "Oscillator", "description": "Dual oscillator confirmation: requires both Stochastic K/D crossover and RSI alignment.", "parameters": {"rsi_len": 14, "stoch_k": 14, "stoch_d": 3}, "code": ''' //@version=5 strategy("Stoch + RSI Combo", overlay=true, default_qty_type=strategy.percent_of_equity, default_qty_value=100, commission_type=strategy.commission.percent, commission_value=0.1) rsi_len = input.int({rsi_len}, title="RSI Length") stoch_k = input.int({stoch_k}, title="Stoch K") stoch_d = input.int({stoch_d}, title="Stoch D Smoothing") stop_loss_pct = input.float(4.0, title="Stop Loss %") rsi_val = ta.rsi(close, rsi_len) k = ta.stoch(close, high, low, stoch_k) d = ta.sma(k, stoch_d) long_cond = ta.crossover(k, d) and k < 30 and rsi_val < 40 exit_cond = (k > 80 and rsi_val > 70) or ta.crossunder(k, d) if long_cond strategy.entry("Long", strategy.long) if exit_cond strategy.close("Long") strategy.exit("SL", "Long", stop=strategy.position_avg_price * (1 - stop_loss_pct / 100)) ''', }, "mean_reversion_zscore": { "id": "mean_reversion_zscore", "name": "Z-Score Mean Reversion", "category": "Statistical", "description": "Statistical mean reversion using Z-score. Enters when price deviates significantly from mean.", "parameters": {"lookback": 50, "entry_z": 2.0, "exit_z": 0.0}, "code": ''' //@version=5 strategy("Z-Score Mean Reversion", overlay=true, default_qty_type=strategy.percent_of_equity, default_qty_value=100, commission_type=strategy.commission.percent, commission_value=0.1) lookback = input.int({lookback}, title="Lookback Period") entry_z = input.float({entry_z}, title="Entry Z-Score") exit_z = input.float({exit_z}, title="Exit Z-Score") stop_loss_pct = input.float(5.0, title="Stop Loss %") mean = ta.sma(close, lookback) sd = ta.stdev(close, lookback) z_score = (close - mean) / sd long_cond = z_score < -entry_z exit_long = z_score > -exit_z if long_cond strategy.entry("Long", strategy.long) if exit_long strategy.close("Long") strategy.exit("SL", "Long", stop=strategy.position_avg_price * (1 - stop_loss_pct / 100)) hline(0, color=color.gray) ''', }, "atr_trailing_stop": { "id": "atr_trailing_stop", "name": "ATR Trailing Stop", "category": "Risk-Managed", "description": "Trend-following with dynamic ATR-based trailing stop. Captures trends with tight risk control.", "parameters": {"atr_length": 14, "atr_multiplier": 2.5, "ema_length": 50}, "code": ''' //@version=5 strategy("ATR Trailing Stop", overlay=true, default_qty_type=strategy.percent_of_equity, default_qty_value=100, commission_type=strategy.commission.percent, commission_value=0.1) atr_len = input.int({atr_length}, title="ATR Length") atr_mult = input.float({atr_multiplier}, title="ATR Multiplier") ema_len = input.int({ema_length}, title="EMA Length") atr_val = ta.atr(atr_len) ema_val = ta.ema(close, ema_len) long_cond = close > ema_val and close > close[1] if long_cond and strategy.position_size == 0 strategy.entry("Long", strategy.long) trail_stop = strategy.position_avg_price > 0 ? close - atr_val * atr_mult : na strategy.exit("Trail", "Long", trail_offset=atr_val * atr_mult / syminfo.mintick) plot(ema_val, color=color.blue, title="EMA") ''', }, "multi_timeframe": { "id": "multi_timeframe", "name": "Multi-Timeframe Confirmation", "category": "Advanced", "description": "Uses higher timeframe trend confirmation with lower timeframe entry. Professional-grade approach.", "parameters": {"htf": "D", "ltf_ema": 20, "htf_ema": 50}, "code": ''' //@version=5 strategy("Multi-TF Confirmation", overlay=true, default_qty_type=strategy.percent_of_equity, default_qty_value=100, commission_type=strategy.commission.percent, commission_value=0.1) htf = input.timeframe("{htf}", title="Higher Timeframe") ltf_ema_len = input.int({ltf_ema}, title="LTF EMA Length") htf_ema_len = input.int({htf_ema}, title="HTF EMA Length") stop_loss_pct = input.float(3.0, title="Stop Loss %") take_profit_pct = input.float(6.0, title="Take Profit %") ltf_ema = ta.ema(close, ltf_ema_len) htf_close = request.security(syminfo.tickerid, htf, close) htf_ema = request.security(syminfo.tickerid, htf, ta.ema(close, htf_ema_len)) htf_bullish = htf_close > htf_ema ltf_signal = ta.crossover(close, ltf_ema) long_cond = htf_bullish and ltf_signal exit_cond = close < ltf_ema and not htf_bullish if long_cond strategy.entry("Long", strategy.long) if exit_cond strategy.close("Long") strategy.exit("SL/TP", "Long", stop=strategy.position_avg_price * (1 - stop_loss_pct / 100), limit=strategy.position_avg_price * (1 + take_profit_pct / 100)) plot(ltf_ema, color=color.blue, title="LTF EMA") bgcolor(htf_bullish ? color.new(color.green, 95) : color.new(color.red, 95)) ''', }, "vwap_strategy": { "id": "vwap_strategy", "name": "VWAP Bounce", "category": "Intraday", "description": "Institutional VWAP-based strategy. Buys on pullback to VWAP with volume confirmation.", "parameters": {"vol_mult": 1.5}, "code": ''' //@version=5 strategy("VWAP Bounce", overlay=true, default_qty_type=strategy.percent_of_equity, default_qty_value=100, commission_type=strategy.commission.percent, commission_value=0.1) vol_mult = input.float({vol_mult}, title="Volume Multiplier") stop_loss_pct = input.float(2.0, title="Stop Loss %") take_profit_pct = input.float(4.0, title="Take Profit %") vwap_val = ta.vwap vol_avg = ta.sma(volume, 20) high_vol = volume > vol_avg * vol_mult bounce = close > vwap_val and close[1] <= vwap_val[1] and high_vol if bounce strategy.entry("Long", strategy.long) if close < vwap_val and strategy.position_size > 0 strategy.close("Long") strategy.exit("SL/TP", "Long", stop=strategy.position_avg_price * (1 - stop_loss_pct / 100), limit=strategy.position_avg_price * (1 + take_profit_pct / 100)) plot(vwap_val, color=color.purple, title="VWAP", linewidth=2) ''', }, "ichimoku_cloud": { "id": "ichimoku_cloud", "name": "Ichimoku Cloud", "category": "Multi-Signal", "description": "Complete Ichimoku Kinko Hyo system with cloud, conversion, and base line signals.", "parameters": {"conv": 9, "base": 26, "span": 52}, "code": ''' //@version=5 strategy("Ichimoku Cloud Strategy", overlay=true, default_qty_type=strategy.percent_of_equity, default_qty_value=100, commission_type=strategy.commission.percent, commission_value=0.1) conv_len = input.int({conv}, title="Conversion Length") base_len = input.int({base}, title="Base Length") span_len = input.int({span}, title="Span B Length") stop_loss_pct = input.float(4.0, title="Stop Loss %") donchian(len) => math.avg(ta.lowest(len), ta.highest(len)) conv_line = donchian(conv_len) base_line = donchian(base_len) lead_a = math.avg(conv_line, base_line) lead_b = donchian(span_len) above_cloud = close > lead_a and close > lead_b tk_cross = ta.crossover(conv_line, base_line) long_cond = tk_cross and above_cloud exit_cond = ta.crossunder(conv_line, base_line) or close < lead_b if long_cond strategy.entry("Long", strategy.long) if exit_cond strategy.close("Long") strategy.exit("SL", "Long", stop=strategy.position_avg_price * (1 - stop_loss_pct / 100)) plot(conv_line, color=color.blue, title="Conversion") plot(base_line, color=color.red, title="Base") p1 = plot(lead_a, offset=base_len, color=color.green, title="Lead A") p2 = plot(lead_b, offset=base_len, color=color.red, title="Lead B") fill(p1, p2, color=lead_a > lead_b ? color.new(color.green, 90) : color.new(color.red, 90)) ''', }, # ── Hedge-Focused Strategies ────────────────────────────────────────── "portfolio_hedge": { "id": "portfolio_hedge", "name": "Dynamic Portfolio Hedge", "category": "Hedging", "description": "Automatically sizes inverse hedging position based on SMA trend + volatility regime. Increases hedge in high-vol downtrends, reduces in calm uptrends.", "parameters": {"trend_sma": 50, "vol_lookback": 20, "max_hedge_pct": 50, "min_hedge_pct": 5}, "code": ''' //@version=5 strategy("Dynamic Portfolio Hedge", overlay=true, default_qty_type=strategy.percent_of_equity, default_qty_value=100, commission_type=strategy.commission.percent, commission_value=0.1) // Inputs trend_len = input.int({trend_sma}, title="Trend SMA Length") vol_len = input.int({vol_lookback}, title="Volatility Lookback") max_hedge = input.float({max_hedge_pct}, title="Max Hedge %", minval=1, maxval=100) min_hedge = input.float({min_hedge_pct}, title="Min Hedge %", minval=0, maxval=50) // Core calculations trend_sma = ta.sma(close, trend_len) ret = math.log(close / close[1]) hist_vol = ta.stdev(ret, vol_len) * math.sqrt(252) * 100 norm_vol = math.min(hist_vol / 30.0, 2.0) // Normalized: 30% vol = 1.0 // Regime detection below_trend = close < trend_sma trend_distance = (close - trend_sma) / trend_sma * 100 // Dynamic hedge sizing hedge_score = 0.0 hedge_score := below_trend ? math.min(max_hedge, min_hedge + math.abs(trend_distance) * norm_vol * 10) : min_hedge // Signals hedge_up = hedge_score > hedge_score[1] * 1.2 and hedge_score > 15 hedge_down = hedge_score < hedge_score[1] * 0.7 and hedge_score < 10 if hedge_up and strategy.position_size <= 0 strategy.entry("Hedge Short", strategy.short, qty=strategy.equity * hedge_score / 100 / close) if hedge_down strategy.close("Hedge Short") // Visuals plot(trend_sma, color=color.blue, title="Trend SMA", linewidth=2) plot(hedge_score, color=color.orange, title="Hedge Score %", display=display.pane) bgcolor(below_trend ? color.new(color.red, 95) : color.new(color.green, 95)) hline(max_hedge, color=color.red, linestyle=hline.style_dashed, title="Max Hedge") hline(min_hedge, color=color.green, linestyle=hline.style_dashed, title="Min Hedge") ''', }, "pairs_trading_hedge": { "id": "pairs_trading_hedge", "name": "Pairs Trading Hedge", "category": "Hedging", "description": "Statistical arbitrage between correlated pairs. Uses z-score of price ratio for mean reversion entries. Market-neutral hedging approach.", "parameters": {"lookback": 60, "entry_z": 2.0, "exit_z": 0.5, "stop_z": 3.5}, "code": ''' //@version=5 strategy("Pairs Trading Hedge", overlay=true, default_qty_type=strategy.percent_of_equity, default_qty_value=100, commission_type=strategy.commission.percent, commission_value=0.1) // Inputs lookback = input.int({lookback}, title="Lookback Period") entry_z = input.float({entry_z}, title="Entry Z-Score") exit_z = input.float({exit_z}, title="Exit Z-Score") stop_z = input.float({stop_z}, title="Stop Loss Z-Score") // Spread calculation (self-referencing mean reversion) mean_price = ta.sma(close, lookback) std_price = ta.stdev(close, lookback) z_score = (close - mean_price) / std_price // Entry signals long_entry = z_score < -entry_z // Oversold: buy short_entry = z_score > entry_z // Overbought: short (hedge) // Exit signals long_exit = z_score > -exit_z short_exit = z_score < exit_z // Stop loss long_stop = z_score < -stop_z short_stop = z_score > stop_z // Execution if long_entry strategy.entry("Long Pair", strategy.long) if short_entry strategy.entry("Short Hedge", strategy.short) if long_exit or long_stop strategy.close("Long Pair") if short_exit or short_stop strategy.close("Short Hedge") // Visualization plot(z_score, color=color.blue, title="Z-Score", display=display.pane) hline(entry_z, color=color.red, linestyle=hline.style_dashed) hline(-entry_z, color=color.green, linestyle=hline.style_dashed) hline(0, color=color.gray) bgcolor(z_score > entry_z ? color.new(color.red, 90) : z_score < -entry_z ? color.new(color.green, 90) : na) ''', }, "tail_risk_hedge": { "id": "tail_risk_hedge", "name": "Tail Risk Protection", "category": "Hedging", "description": "Simulates protective put strategy. Activates hedging when implied volatility spikes and trend breaks down. Protects against black swan events.", "parameters": {"vol_threshold": 25, "trend_ma": 200, "hedge_trigger_pct": 5}, "code": ''' //@version=5 strategy("Tail Risk Protection", overlay=true, default_qty_type=strategy.percent_of_equity, default_qty_value=100, commission_type=strategy.commission.percent, commission_value=0.1) // Inputs vol_thresh = input.float({vol_threshold}, title="Volatility Threshold %") trend_ma_len = input.int({trend_ma}, title="Trend MA Length") hedge_trigger = input.float({hedge_trigger_pct}, title="Hedge Trigger Drop %") // Calculations ret = math.log(close / close[1]) hist_vol = ta.stdev(ret, 20) * math.sqrt(252) * 100 trend_line = ta.sma(close, trend_ma_len) recent_high = ta.highest(close, 20) drawdown_pct = (recent_high - close) / recent_high * 100 // Tail risk detection vol_spike = hist_vol > vol_thresh trend_break = close < trend_line sharp_drop = drawdown_pct > hedge_trigger tail_risk = vol_spike and (trend_break or sharp_drop) // Hedge activation if tail_risk and strategy.position_size >= 0 strategy.entry("Tail Hedge", strategy.short) // Release hedge when volatility normalizes + trend recovers if hist_vol < vol_thresh * 0.7 and close > trend_line strategy.close("Tail Hedge") // Visuals plot(trend_line, color=color.blue, title="200 MA", linewidth=2) plot(hist_vol, color=vol_spike ? color.red : color.green, title="Hist Vol %", display=display.pane) bgcolor(tail_risk ? color.new(color.red, 85) : na) plotshape(tail_risk and not tail_risk[1], title="Hedge Activated", style=shape.triangledown, location=location.abovebar, color=color.red, size=size.small) ''', }, "correlation_hedge": { "id": "correlation_hedge", "name": "Correlation Hedge", "category": "Hedging", "description": "Trades inverse-correlated asset when primary shows weakness. Uses rolling correlation and momentum divergence for hedge timing.", "parameters": {"corr_lookback": 30, "momentum_len": 14, "hedge_threshold": -0.5}, "code": ''' //@version=5 strategy("Correlation Hedge Strategy", overlay=true, default_qty_type=strategy.percent_of_equity, default_qty_value=100, commission_type=strategy.commission.percent, commission_value=0.1) // Inputs corr_len = input.int({corr_lookback}, title="Correlation Lookback") mom_len = input.int({momentum_len}, title="Momentum Length") stop_loss_pct = input.float(5.0, title="Stop Loss %") take_profit_pct = input.float(10.0, title="Take Profit %") // Momentum analysis momentum = ta.mom(close, mom_len) mom_sma = ta.sma(momentum, mom_len) momentum_weakening = momentum < 0 and momentum < mom_sma // Volatility regime ret = math.log(close / close[1]) hist_vol = ta.stdev(ret, 20) * math.sqrt(252) * 100 rising_vol = hist_vol > ta.sma(hist_vol, 50) // Trend analysis ma_50 = ta.sma(close, 50) ma_200 = ta.sma(close, 200) bearish_trend = ma_50 < ma_200 // Hedge activation: weakness + rising vol + bearish trend hedge_signal = momentum_weakening and rising_vol and bearish_trend hedge_exit = momentum > 0 and not rising_vol if hedge_signal and strategy.position_size >= 0 strategy.entry("Corr Hedge", strategy.short) if hedge_exit strategy.close("Corr Hedge") strategy.exit("Exit", "Corr Hedge", stop=strategy.position_avg_price * (1 + stop_loss_pct / 100), limit=strategy.position_avg_price * (1 - take_profit_pct / 100)) // Visuals plot(ma_50, color=color.blue, title="50 MA") plot(ma_200, color=color.red, title="200 MA") plot(momentum, color=momentum > 0 ? color.green : color.red, title="Momentum", display=display.pane) bgcolor(hedge_signal ? color.new(color.orange, 90) : na) ''', }, # ── Global Market & Multi-Asset Strategies ──────────────────────────── "forex_session_breakout": { "id": "forex_session_breakout", "name": "Forex Session Breakout", "category": "Forex", "description": "Trades breakouts from London, New York, and Tokyo session ranges. Optimized for major forex pairs like EUR/USD, GBP/JPY.", "parameters": {"london_start": 8, "london_end": 16, "ny_start": 13, "ny_end": 21, "atr_mult": 1.5}, "code": ''' //@version=5 strategy("Forex Session Breakout", overlay=true, default_qty_type=strategy.percent_of_equity, default_qty_value=100, commission_type=strategy.commission.percent, commission_value=0.02) // Inputs london_start = input.int({london_start}, title="London Open (UTC)", minval=0, maxval=23) london_end = input.int({london_end}, title="London Close (UTC)", minval=0, maxval=23) ny_start = input.int({ny_start}, title="NY Open (UTC)", minval=0, maxval=23) atr_len = input.int(14, title="ATR Length") atr_mult = input.float({atr_mult}, title="ATR Multiplier", step=0.1) stop_atr = input.float(1.0, title="Stop Loss ATR Multiplier", step=0.1) // Session detection (UTC) cur_hour = hour(time, "UTC") in_london = cur_hour >= london_start and cur_hour < london_end in_ny = cur_hour >= ny_start and cur_hour < ny_start + 8 // Session high/low tracking var float session_high = na var float session_low = na new_session = (cur_hour == london_start or cur_hour == ny_start) and (cur_hour[1] != cur_hour) if new_session session_high := high session_low := low else if in_london or in_ny session_high := math.max(nz(session_high), high) session_low := math.min(nz(session_low), low) atr = ta.atr(atr_len) breakout_up = ta.crossover(close, session_high + atr * atr_mult * 0.1) breakout_down = ta.crossunder(close, session_low - atr * atr_mult * 0.1) if breakout_up and (in_london or in_ny) strategy.entry("Long", strategy.long) if breakout_down and (in_london or in_ny) strategy.entry("Short", strategy.short) strategy.exit("Exit L", "Long", stop=strategy.position_avg_price - atr * stop_atr, limit=strategy.position_avg_price + atr * atr_mult) strategy.exit("Exit S", "Short", stop=strategy.position_avg_price + atr * stop_atr, limit=strategy.position_avg_price - atr * atr_mult) bgcolor(in_london ? color.new(color.blue, 95) : in_ny ? color.new(color.orange, 95) : na) plot(session_high, color=color.green, style=plot.style_circles, title="Session High") plot(session_low, color=color.red, style=plot.style_circles, title="Session Low") ''', }, "crypto_momentum": { "id": "crypto_momentum", "name": "Crypto Momentum Hunter", "category": "Crypto", "description": "24/7 crypto momentum strategy using EMA + volume surge detection. Works on BTC, ETH, SOL, and altcoin pairs.", "parameters": {"fast_ema": 9, "slow_ema": 21, "vol_surge_mult": 2.0}, "code": ''' //@version=5 strategy("Crypto Momentum Hunter", overlay=true, default_qty_type=strategy.percent_of_equity, default_qty_value=100, commission_type=strategy.commission.percent, commission_value=0.075) // Inputs (crypto-optimized defaults) fast_ema_len = input.int({fast_ema}, title="Fast EMA", minval=1) slow_ema_len = input.int({slow_ema}, title="Slow EMA", minval=1) vol_surge = input.float({vol_surge_mult}, title="Volume Surge Multiplier", step=0.1) rsi_len = input.int(14, title="RSI Length") trailing_pct = input.float(5.0, title="Trailing Stop %", step=0.5) // Indicators fast_ema = ta.ema(close, fast_ema_len) slow_ema = ta.ema(close, slow_ema_len) rsi = ta.rsi(close, rsi_len) vol_avg = ta.sma(volume, 20) vol_spike = volume > vol_avg * vol_surge // Trend + momentum + volume confirmation bullish = ta.crossover(fast_ema, slow_ema) and rsi > 50 and vol_spike bearish = ta.crossunder(fast_ema, slow_ema) and rsi < 50 // Entries if bullish strategy.entry("Long", strategy.long) if bearish strategy.close("Long") strategy.entry("Short", strategy.short) if ta.crossover(fast_ema, slow_ema) and strategy.position_size < 0 strategy.close("Short") // Trailing stop strategy.exit("Trail L", "Long", trail_price=strategy.position_avg_price, trail_offset=close * trailing_pct / 100 / syminfo.mintick) strategy.exit("Trail S", "Short", trail_price=strategy.position_avg_price, trail_offset=close * trailing_pct / 100 / syminfo.mintick) // Visuals plot(fast_ema, color=color.new(color.lime, 0), title="Fast EMA") plot(slow_ema, color=color.new(color.orange, 0), title="Slow EMA") plotshape(vol_spike, style=shape.diamond, location=location.belowbar, color=color.yellow, size=size.tiny, title="Vol Surge") bgcolor(strategy.position_size > 0 ? color.new(color.green, 93) : strategy.position_size < 0 ? color.new(color.red, 93) : na) ''', }, "nifty_orb_intraday": { "id": "nifty_orb_intraday", "name": "Nifty/Indian Market ORB", "category": "Intraday", "description": "Opening Range Breakout for Indian markets (NSE/BSE). Captures first 15-min range breakout on NIFTY50, Bank Nifty, or individual stocks.", "parameters": {"orb_minutes": 15, "target_mult": 2.0, "sl_buffer_pct": 0.1}, "code": ''' //@version=5 strategy("ORB - Indian Market Intraday", overlay=true, default_qty_type=strategy.percent_of_equity, default_qty_value=100, commission_type=strategy.commission.percent, commission_value=0.03) // Inputs orb_minutes = input.int({orb_minutes}, title="ORB Minutes", options=[5, 15, 30]) target_mult = input.float({target_mult}, title="Target Multiplier (x Range)", step=0.1) sl_buffer = input.float({sl_buffer_pct}, title="SL Buffer %", step=0.05) session_end = input.int(1500, title="Exit by (HHMM)", tooltip="Close all positions by this time. Indian market closes at 1530.") // Session tracking (IST = UTC+5:30) is_new_day = ta.change(time("D")) != 0 cur_time_int = hour * 100 + minute // ORB range calculation var float orb_high = na var float orb_low = na var bool orb_set = false if is_new_day orb_high := high orb_low := low orb_set := false else if not orb_set orb_high := math.max(nz(orb_high), high) orb_low := math.min(nz(orb_low), low) if bar_index - ta.valuewhen(is_new_day, bar_index, 0) >= orb_minutes / timeframe.multiplier orb_set := true orb_range = orb_high - orb_low target_pts = orb_range * target_mult // Breakout entries (only after ORB is set, before session end) long_entry = orb_set and ta.crossover(close, orb_high * (1 + sl_buffer / 100)) and cur_time_int < session_end short_entry = orb_set and ta.crossunder(close, orb_low * (1 - sl_buffer / 100)) and cur_time_int < session_end if long_entry strategy.entry("ORB Long", strategy.long) if short_entry strategy.entry("ORB Short", strategy.short) // Targets and stops strategy.exit("Exit L", "ORB Long", stop=orb_low, limit=orb_high + target_pts) strategy.exit("Exit S", "ORB Short", stop=orb_high, limit=orb_low - target_pts) // Force close at session end if cur_time_int >= session_end strategy.close_all(comment="Session End") // Visuals plot(orb_set ? orb_high : na, color=color.green, style=plot.style_linebr, linewidth=2, title="ORB High") plot(orb_set ? orb_low : na, color=color.red, style=plot.style_linebr, linewidth=2, title="ORB Low") bgcolor(is_new_day ? color.new(color.white, 95) : na) ''', }, "commodity_trend": { "id": "commodity_trend", "name": "Commodity Trend Follower", "category": "Commodities", "description": "Donchian Channel breakout system for commodities (Gold, Silver, Crude Oil, Natural Gas). Classic turtle-trading inspired approach.", "parameters": {"entry_len": 55, "exit_len": 20, "atr_stop": 3.0}, "code": ''' //@version=5 strategy("Commodity Trend Follower", overlay=true, default_qty_type=strategy.percent_of_equity, default_qty_value=100, commission_type=strategy.commission.percent, commission_value=0.05) // Inputs entry_len = input.int({entry_len}, title="Entry Channel Length (Donchian)") exit_len = input.int({exit_len}, title="Exit Channel Length") atr_stop = input.float({atr_stop}, title="ATR Trailing Stop Multiplier", step=0.1) atr_len = input.int(20, title="ATR Length") // Donchian Channels entry_high = ta.highest(high, entry_len) entry_low = ta.lowest(low, entry_len) exit_high = ta.highest(high, exit_len) exit_low = ta.lowest(low, exit_len) atr = ta.atr(atr_len) // Trend filter — only trade in direction of 100-period SMA sma_100 = ta.sma(close, 100) up_trend = close > sma_100 dn_trend = close < sma_100 // Entries — breakout of Donchian channel long_entry = ta.crossover(close, entry_high[1]) and up_trend short_entry = ta.crossunder(close, entry_low[1]) and dn_trend if long_entry strategy.entry("Long", strategy.long) if short_entry strategy.entry("Short", strategy.short) // Exits — opposite Donchian channel or ATR trailing stop if strategy.position_size > 0 and close < exit_low[1] strategy.close("Long", comment="Donchian Exit") if strategy.position_size < 0 and close > exit_high[1] strategy.close("Short", comment="Donchian Exit") strategy.exit("Trail L", "Long", stop=strategy.position_avg_price - atr * atr_stop) strategy.exit("Trail S", "Short", stop=strategy.position_avg_price + atr * atr_stop) // Visuals upper = plot(entry_high, color=color.new(color.green, 50), title="Entry High") lower = plot(entry_low, color=color.new(color.red, 50), title="Entry Low") fill(upper, lower, color=color.new(color.gray, 95)) plot(sma_100, color=color.white, linewidth=2, title="100 SMA Trend") ''', }, "futures_scalping": { "id": "futures_scalping", "name": "Futures Intraday Scalper", "category": "Futures", "description": "High-frequency intraday scalping for futures (ES, NQ, NF, BNF, CL). Uses VWAP + EMA pullback entries with tight risk management.", "parameters": {"ema_len": 9, "risk_reward": 2.0, "atr_sl_mult": 1.0}, "code": ''' //@version=5 strategy("Futures Intraday Scalper", overlay=true, default_qty_type=strategy.percent_of_equity, default_qty_value=100, commission_type=strategy.commission.percent, commission_value=0.02) // Inputs ema_len = input.int({ema_len}, title="Fast EMA Length") rr_ratio = input.float({risk_reward}, title="Risk:Reward Ratio", step=0.1) atr_sl_mult = input.float({atr_sl_mult}, title="ATR Stop Multiplier", step=0.1) atr_len = input.int(14, title="ATR Length") max_trades = input.int(6, title="Max Trades Per Day") // Indicators ema_fast = ta.ema(close, ema_len) ema_mid = ta.ema(close, 21) vwap = ta.vwap(hlc3) atr = ta.atr(atr_len) rsi = ta.rsi(close, 7) // Count today's trades var int trades_today = 0 if ta.change(time("D")) != 0 trades_today := 0 // Bullish scalp: price above VWAP, pulls back to EMA, then bounces bull_setup = close > vwap and low <= ema_fast and close > ema_fast and rsi > 40 and rsi < 70 bear_setup = close < vwap and high >= ema_fast and close < ema_fast and rsi < 60 and rsi > 30 // Entries with trade count limit if bull_setup and trades_today < max_trades strategy.entry("Scalp L", strategy.long) trades_today += 1 if bear_setup and trades_today < max_trades strategy.entry("Scalp S", strategy.short) trades_today += 1 // Tight exits sl = atr * atr_sl_mult tp = sl * rr_ratio strategy.exit("Exit L", "Scalp L", stop=strategy.position_avg_price - sl, limit=strategy.position_avg_price + tp) strategy.exit("Exit S", "Scalp S", stop=strategy.position_avg_price + sl, limit=strategy.position_avg_price - tp) // Visuals plot(vwap, color=color.new(color.yellow, 0), linewidth=2, title="VWAP") plot(ema_fast, color=color.new(color.cyan, 0), title="Fast EMA") plot(ema_mid, color=color.new(color.gray, 30), title="21 EMA") bgcolor(close > vwap ? color.new(color.green, 97) : color.new(color.red, 97)) ''', }, } # ── LLM Generator ─────────────────────────────────────────────────────── PINE_SCRIPT_SYSTEM_PROMPT = """You are an expert TradingView Pine Script v5 developer. Generate ONLY valid Pine Script v5 code. Follow these rules: 1. Always start with //@version=5 2. Use strategy() for backtestable strategies 3. Use input.int(), input.float(), input.bool() for user parameters 4. Use ta.* namespace for all built-in indicators 5. Use strategy.entry() and strategy.exit() for order management 6. Include stop loss and take profit via strategy.exit() 7. Add plot() calls for visual indicators on chart 8. Use proper variable naming (snake_case) 9. Add commission via strategy() declaration 10. Make the code clean, well-commented, and production-ready Output ONLY the Pine Script code, nothing else.""" async def generate_from_description( description: str, parameters: Optional[Dict[str, Any]] = None, ) -> Dict[str, Any]: """ Generate Pine Script v5 code from a natural language description. Uses Groq LLM for intelligent code generation. """ if not _settings.groq_api_key: # Fallback: find closest template match return _template_fallback(description) user_prompt = f"Generate a complete TradingView Pine Script v5 strategy for:\n\n{description}" if parameters: user_prompt += f"\n\nUse these parameters: {parameters}" try: async with aiohttp.ClientSession() as session: async with session.post( "https://api.groq.com/openai/v1/chat/completions", headers={ "Authorization": f"Bearer {_settings.groq_api_key}", "Content-Type": "application/json", }, json={ "model": "llama-3.3-70b-versatile", "messages": [ {"role": "system", "content": PINE_SCRIPT_SYSTEM_PROMPT}, {"role": "user", "content": user_prompt}, ], "temperature": 0.2, "max_tokens": 3000, }, timeout=aiohttp.ClientTimeout(total=30), ) as resp: if resp.status != 200: return _template_fallback(description) data = await resp.json() code = data["choices"][0]["message"]["content"] # Clean code (remove markdown fences if present) if "```" in code: parts = code.split("```") for part in parts: if "//@version=5" in part: code = part.replace("pine", "", 1).strip() break return { "code": code, "source": "llm", "description": description, "valid": "//@version=5" in code, } except Exception as e: logger.warning("LLM generation failed: %s", e) return _template_fallback(description) def generate_from_template( template_id: str, parameters: Optional[Dict[str, Any]] = None, ) -> Dict[str, Any]: """Generate Pine Script from a pre-built template.""" template = STRATEGY_TEMPLATES.get(template_id) if not template: raise ValueError(f"Template '{template_id}' not found") # Merge default params with overrides params = {**template["parameters"]} if parameters: params.update(parameters) # Format code with parameters code = template["code"].strip() for key, value in params.items(): code = code.replace(f"{{{key}}}", str(value)) return { "code": code, "source": "template", "template_id": template_id, "template_name": template["name"], "parameters": params, "valid": True, } async def customize_code( existing_code: str, modification: str, ) -> Dict[str, Any]: """Modify existing Pine Script via LLM.""" if not _settings.groq_api_key: return {"code": existing_code, "source": "unchanged", "error": "LLM unavailable"} try: async with aiohttp.ClientSession() as session: async with session.post( "https://api.groq.com/openai/v1/chat/completions", headers={ "Authorization": f"Bearer {_settings.groq_api_key}", "Content-Type": "application/json", }, json={ "model": "llama-3.3-70b-versatile", "messages": [ {"role": "system", "content": PINE_SCRIPT_SYSTEM_PROMPT}, {"role": "user", "content": f"Modify this Pine Script code:\n\n```\n{existing_code}\n```\n\nModification: {modification}\n\nOutput only the complete modified code."}, ], "temperature": 0.2, "max_tokens": 3000, }, timeout=aiohttp.ClientTimeout(total=30), ) as resp: if resp.status != 200: return {"code": existing_code, "source": "unchanged", "error": "LLM failed"} data = await resp.json() code = data["choices"][0]["message"]["content"] if "```" in code: parts = code.split("```") for part in parts: if "//@version=5" in part: code = part.replace("pine", "", 1).strip() break return {"code": code, "source": "llm_modified", "valid": "//@version=5" in code} except Exception as e: logger.warning("LLM customization failed: %s", e) return {"code": existing_code, "source": "unchanged", "error": str(e)} def get_all_templates() -> List[Dict[str, Any]]: """Return all available templates (without full code for listing).""" return [ { "id": t["id"], "name": t["name"], "category": t["category"], "description": t["description"], "parameters": t["parameters"], } for t in STRATEGY_TEMPLATES.values() ] def _template_fallback(description: str) -> Dict[str, Any]: """Keyword-based template matching when LLM is unavailable.""" desc_lower = description.lower() best_match = "sma_crossover" # default keywords = { "rsi": "rsi_reversal", "macd": "macd_signal", "bollinger": "bollinger_breakout", "supertrend": "supertrend", "ema": "ema_ribbon", "stochastic": "stochastic_rsi_combo", "z-score": "mean_reversion_zscore", "mean reversion": "mean_reversion_zscore", "trailing": "atr_trailing_stop", "atr": "atr_trailing_stop", "multi": "multi_timeframe", "timeframe": "multi_timeframe", "vwap": "vwap_strategy", "ichimoku": "ichimoku_cloud", "cloud": "ichimoku_cloud", "crossover": "sma_crossover", "moving average": "sma_crossover", "hedge": "portfolio_hedge", "hedging": "portfolio_hedge", "portfolio hedge": "portfolio_hedge", "pairs": "pairs_trading_hedge", "pairs trading": "pairs_trading_hedge", "arbitrage": "pairs_trading_hedge", "tail risk": "tail_risk_hedge", "black swan": "tail_risk_hedge", "protection": "tail_risk_hedge", "protective": "tail_risk_hedge", "correlation": "correlation_hedge", "inverse": "correlation_hedge", "forex": "forex_session_breakout", "session": "forex_session_breakout", "london": "forex_session_breakout", "currency": "forex_session_breakout", "crypto": "crypto_momentum", "bitcoin": "crypto_momentum", "btc": "crypto_momentum", "eth": "crypto_momentum", "altcoin": "crypto_momentum", "nifty": "nifty_orb_intraday", "indian": "nifty_orb_intraday", "nse": "nifty_orb_intraday", "orb": "nifty_orb_intraday", "opening range": "nifty_orb_intraday", "bank nifty": "nifty_orb_intraday", "commodity": "commodity_trend", "gold": "commodity_trend", "crude": "commodity_trend", "oil": "commodity_trend", "silver": "commodity_trend", "donchian": "commodity_trend", "turtle": "commodity_trend", "futures": "futures_scalping", "scalp": "futures_scalping", "scalping": "futures_scalping", "intraday": "nifty_orb_intraday", "f&o": "futures_scalping", } for keyword, template_id in keywords.items(): if keyword in desc_lower: best_match = template_id break result = generate_from_template(best_match) result["source"] = "template_fallback" result["matched_keyword"] = best_match return result