munger-engine / docs /3EMA_crossing.pine
dromerosm's picture
feat: Add authentication, charting, and dark mode
21ac82a
//@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)