//@version=6 indicator("Munger Engine Strategy", overlay=true) // ========================================== // INPUTS // ========================================== grp_fund = "Fundamentals" roe_min = input.float(15.0, "Min ROE (%)", group=grp_fund) de_max = input.float(50.0, "Max Debt/Equity (%)", group=grp_fund) grp_strat = "Strategy (Weekly)" sma_len = input.int(200, "SMA Length", group=grp_strat) sma_len_100 = input.int(100, "SMA 100 Length", group=grp_strat) sma_len_50 = input.int(50, "SMA 50 Length", group=grp_strat) lookback = input.int(4, "Touch Lookback (Weeks)", group=grp_strat) show_markers = input.bool(true, "Show Entry Markers (with Tooltip)", group=grp_strat) grp_risk = "Risk Management (Daily ATR)" atr_len = input.int(14, "ATR Length", group=grp_risk) atr_mult = input.float(2.0, "Stop Loss ATR Mult", group=grp_risk) rr_ratio = input.float(3.0, "Risk/Reward Ratio", group=grp_risk) account_equity = input.float(100000.0, "Account Equity ($)", group=grp_risk) risk_per_trade_pct = input.float(0.5, "Risk Per Trade (%)", group=grp_risk) / 100 entry_limit_buffer_bps = input.float(75.0, "Entry Limit Buffer (bps)", group=grp_risk) // ========================================== // DATA FETCHING (FUNDAMENTALS) // ========================================== // Fetch Quarterly data for ROE and Debt/Equity // We manually calculate D/E to avoid "Symbol resolve error" on some tickers/exchanges roe = request.financial(syminfo.tickerid, "RETURN_ON_EQUITY", "FQ") term_debt = request.financial(syminfo.tickerid, "TOTAL_DEBT", "FQ") term_equity = request.financial(syminfo.tickerid, "TOTAL_EQUITY", "FQ") // Calculate Debt to Equity (%) float de = na if not na(term_debt) and not na(term_equity) and term_equity != 0 de := (term_debt / term_equity) * 100 // Quality Check is_quality = (not na(roe) and roe > roe_min) and (not na(de) and de < de_max) // ========================================== // CALCULATION (WEEKLY TIMEFRAME) // ========================================== // We define a tuple function to run inside request.security to ensure logic runs on Weekly bars // regardless of the chart's timeframe. calc_weekly_strategy() => float ma = ta.sma(close, sma_len) float ma100 = ta.sma(close, sma_len_100) float ma50 = ta.sma(close, sma_len_50) bool touched = false // Check if Low touched MA in the last N weeks for i = 0 to lookback - 1 if low[i] <= ma[i] touched := true bool green = close > open // Cond: Close of previous week AND current week > SMA200 // Note: in 'calc_weekly_strategy' context, close[1] is previous week's close bool above_ma_now = close > ma bool above_ma_prev = close[1] > ma[1] [ma, touched, green, ma100, ma50, above_ma_now, above_ma_prev] // Request Weekly Data // lookahead=barmerge.lookahead_on forces strict non-repainting historical data if desired, // but for an indicator 'off' or default is usually fine for current state. [w_ma, w_touched, w_green, w_ma100, w_ma50, w_above_now, w_above_prev] = request.security(syminfo.tickerid, "W", calc_weekly_strategy()) // Fetch Daily ATR for Risk Calculation (matches engine logic) d_atr = request.security(syminfo.tickerid, "D", ta.atr(atr_len)) // ========================================== // SIGNALS // ========================================== buy_signal = is_quality and w_touched and w_green and w_above_now and w_above_prev // ========================================== // PLOTTING // ========================================== plot(w_ma, "Weekly SMA 200", color=color.blue, linewidth=2) plot(w_ma100, "Weekly SMA 100", color=color.orange, linewidth=1) plot(w_ma50, "Weekly SMA 50", color=color.yellow, linewidth=1) // Plot Signal // We combine Marker and Distance Info into a single label with tooltip for "Hover" effect if buy_signal and show_markers // Distance dist = ((close - w_ma) / w_ma) * 100 txt_dist = str.tostring(dist, "#.2") + "%" // Trade Plan Calculation entry_stop = high + syminfo.mintick entry_limit = entry_stop * (1 + entry_limit_buffer_bps / 10000) risk = d_atr * atr_mult sl_price = entry_stop - risk tp_price = entry_stop + (risk * rr_ratio) // Position Sizing risk_dollars = account_equity * risk_per_trade_pct // Check for valid risk to avoid division by zero float shares = 0.0 if risk > 0 shares := math.floor(risk_dollars / risk) position_value = shares * entry_stop // Build Tooltip tt = "Distance to SMA: " + txt_dist + "\n" + "------------------------\n" + "TRADE PLAN (Daily ATR)\n" + "Order Type: STOP-LIMIT\n" + "Buy Stop: " + str.tostring(entry_stop, format.mintick) + "\n" + "Buy Limit: " + str.tostring(entry_limit, format.mintick) + "\n" + "Stop Loss: " + str.tostring(sl_price, format.mintick) + "\n" + "Take Profit: " + str.tostring(tp_price, format.mintick) + "\n" + "Risk (R): " + str.tostring(risk, format.mintick) + "\n" + "------------------------\n" + "POSITION SIZING\n" + "Shares: " + str.tostring(shares, "#") + "\n" + "Value: $" + str.tostring(position_value, "#.##") + "\n" + "Risked: $" + str.tostring(risk_dollars, "#.##") label.new(bar_index, low, text="", color=color.green, style=label.style_triangleup, size=size.tiny, yloc=yloc.belowbar, tooltip=tt) // ========================================== // INFO PANEL // ========================================== var table panel = table.new(position.top_right, 2, 4, border_width=1) if barstate.islast // Header table.cell(panel, 0, 0, "Metric", bgcolor=color.gray, text_color=color.white) table.cell(panel, 1, 0, "Value", bgcolor=color.gray, text_color=color.white) // ROE c_roe = (not na(roe) and roe > roe_min) ? color.new(color.green, 60) : color.new(color.red, 60) table.cell(panel, 0, 1, "ROE", bgcolor=c_roe) table.cell(panel, 1, 1, str.tostring(roe, format.percent), bgcolor=c_roe) // D/E c_de = (not na(de) and de < de_max) ? color.new(color.green, 60) : color.new(color.red, 60) table.cell(panel, 0, 2, "Debt/Eq", bgcolor=c_de) table.cell(panel, 1, 2, str.tostring(de, format.percent), bgcolor=c_de) // Status status_txt = is_quality ? "QUALITY" : "AVOID" c_status = is_quality ? color.green : color.red table.cell(panel, 0, 3, "Status", bgcolor=c_status, text_color=color.white) table.cell(panel, 1, 3, status_txt, bgcolor=c_status, text_color=color.white)