jashdoshi77's picture
added more tickersfor pine
8577e65
"""
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