06-09-2025, 08:00 AM
Code:
// This Pine Script® code is subject to the terms of the Mozilla Public License 2.0 at https://mozilla.org/MPL/2.0/
// © julzen2
//@version=5
indicator(title="TMA+CG", shorttitle="TMA+CG", overlay=true)
// --- Price Mode Constants ---
int PRICE_MODE_CLOSE = 0
int PRICE_MODE_OPEN = 1
int PRICE_MODE_HIGH = 2
int PRICE_MODE_LOW = 3
int PRICE_MODE_HL2 = 4 // (High + Low) / 2
int PRICE_MODE_HLC3 = 5 // (High + Low + Close) / 3
int PRICE_MODE_OHLC4 = 6 // (Open + High + Low + Close) / 4
int PRICE_MODE_WEIGHTED = 7 // (High + Low + Close * 2) / 4 - This matches MQL4's PRICE_WEIGHTED
// --- Inputs ---
string i_timeframe = input.timeframe("0", "Time Frame (0 for chart TF)")
int i_halfLength = input.int(56, "Half Length", minval=1)
int i_price = input.int(PRICE_MODE_WEIGHTED, "Price", options=[
PRICE_MODE_CLOSE,
PRICE_MODE_OPEN,
PRICE_MODE_HIGH,
PRICE_MODE_LOW,
PRICE_MODE_HL2,
PRICE_MODE_HLC3,
PRICE_MODE_OHLC4,
PRICE_MODE_WEIGHTED
])
float i_bandsDeviations = input.float(2.5, "Bands Deviations", minval=0.0)
bool i_interpolate = input.bool(true, "Interpolate")
bool i_alertsOn = input.bool(false, "Enable Alerts")
bool i_alertsOnCurrent = input.bool(false, "Alerts on Current Bar")
bool i_alertsOnHighLow = input.bool(true, "Alerts on High/Low Penetration")
bool i_alertsMessage = input.bool(true, "Show Alert Message")
bool i_alertsSound = input.bool(false, "Play Alert Sound")
bool i_alertsEmail = input.bool(false, "Send Alert Email")
// --- Global Variables (for alert suppression within the main loop) ---
var string g_previousAlert_msg = ""
var int g_previousAlert_time = 0
// --- Helper Function: Get Price Series ---
f_get_price_series(mode, o, h, l, c) =>
float price_val = switch mode
PRICE_MODE_CLOSE => c
PRICE_MODE_OPEN => o
PRICE_MODE_HIGH => h
PRICE_MODE_LOW => l
PRICE_MODE_HL2 => (h + l) / 2
PRICE_MODE_HLC3 => (h + l + c) / 3
PRICE_MODE_OHLC4 => (o + h + l + c) / 4
PRICE_MODE_WEIGHTED => (h + l + c * 2) / 4
=> c
price_val
// --- Helper Function: Calculate TMA and Bands ---
// IMPORTANT: This calculation results in a centered TMA, which will cause repainting in Pine Script.
f_calculate_tma(price_series, halfLength, bandsDeviations) =>
int hl = math.max(1, halfLength)
int fullLength = 2 * hl + 1 // Total bars for calculation
float tma_val = na
float up_band = na
float dn_band = na
// We need enough bars in history to calculate the TMA
// If not enough history, TMA will be 'na'
if bar_index >= fullLength - 1 // Ensure we have at least 'fullLength' bars
float current_tma_sum = 0.0
float current_tma_sumw = 0.0
// The TMA is now calculated considering data from 'fullLength - 1' bars back
// to the current bar [0], effectively making it a lagging average.
// The original formula looked into the "future" with price_series[-j].
// To make it non-repainting, we must only use past data.
// A common way to get a "Triangular" average in Pine is to smooth an SMA twice.
// For a true "lagging" TMA as an approximation of the centered one without lookahead bias,
// we can take a `2 * hl + 1` simple moving average and then average that result.
// Approach 1: Double smoothed SMA (common approximation for non-repainting TMA)
// Adjust the lengths slightly to match the original TMA concept as much as possible
float sma1 = ta.sma(price_series, hl + 1) // First SMA
tma_val := ta.sma(sma1, hl + 1) // Second SMA of the first SMA
// For bands, we can use standard deviation of the current price around this lagging TMA
float diff_sum_sq_up = 0.0
float diff_sum_sq_dn = 0.0
for i = 0 to fullLength - 1 // Iterate over the data points for standard deviation
float price_diff = price_series[i] - tma_val
if price_diff >= 0
diff_sum_sq_up += math.pow(price_diff, 2)
else
diff_sum_sq_dn += math.pow(price_diff, 2)
up_band := tma_val + bandsDeviations * math.sqrt(diff_sum_sq_up / fullLength)
dn_band := tma_val - bandsDeviations * math.sqrt(diff_sum_sq_dn / fullLength)
[tma_val, up_band, dn_band]
//---
//## Main Calculation and Plotting Logic
//---
string actual_tf = i_timeframe == "0" ? timeframe.period : i_timeframe
[o_tf, h_tf, l_tf, c_tf] = request.security(syminfo.tickerid, actual_tf, [open, high, low, close], lookahead=barmerge.lookahead_on)
float current_price_tf = f_get_price_series(i_price, o_tf, h_tf, l_tf, c_tf)
var float tmBuffer_val = na
var float upBuffer_val = na
var float dnBuffer_val = na
// Corrected: Use := for assignment to existing 'var' variables
if bar_index >= 0
// Declare temporary variables on separate lines
float _tma = na // Initialize with na, or 0.0 if you prefer
float _up = na
float _dn = na
// Declare temporary variables on separate lines, as Pine Script requires
// Assign the results from the function call to these temporary variables using tuple assignment with '='
[temp_tma, temp_up, temp_dn] = f_calculate_tma(current_price_tf, i_halfLength, i_bandsDeviations)
// Then assign them individually to the 'var' variables using the ':=' operator.
tmBuffer_val := temp_tma
upBuffer_val := temp_up
dnBuffer_val := temp_dn
// Corrected plotting colors
plot(tmBuffer_val, title="TMA", color=color.new(color.gray, 0), linewidth=2, style=plot.style_line) // Using color.gray for DimGray
plot(upBuffer_val, title="Upper Band", color=color.new(color.red, 0), linewidth=1, style=plot.style_line)
plot(dnBuffer_val, title= "Lower Band", color=color.new(color.green, 0), linewidth=1, style=plot.style_line) // Using color.lime for LimeGreen
//---
//## Arrow Logic
//---
float prev_high_tf = h_tf[1]
float prev_low_tf = l_tf[1]
float prev_close_tf = c_tf[1]
float prev_open_tf = o_tf[1]
float prev_upBuffer = upBuffer_val[1]
float prev_dnBuffer = dnBuffer_val[1]
float atr_val = ta.atr(20)
bool up_arrow_fire = prev_high_tf > prev_upBuffer and prev_close_tf > prev_open_tf and c_tf[0] < o_tf[0]
//plotshape(up_arrow_fire ? h_tf[0] + atr_val : na,
//title="Up Arrow", location=location.abovebar, color=color.new(color.blue, 0), style=shape.arrowup, size=size.small)
bool dn_arrow_fire = prev_low_tf < prev_dnBuffer and prev_close_tf < prev_open_tf and c_tf[0] > o_tf[0]
//plotshape(dn_arrow_fire ? l_tf[0] - atr_val : na,
//title="Down Arrow", location=location.belowbar, color=color.new(color.red, 0), style=shape.arrowdown, size=size.small)
//---
//## Alert Logic
//---
// --- Main Calculation ---
// Get actual timeframe string for request.security
// This is the correct and only block for main calculations and plotting.
//actual_tf = i_timeframe == "0" ? timeframe.period : i_timeframe
// Request OHLC data from the specified timeframe unconditionally.
// This is valid because `request.security` is called once per bar in this function.
// `lookahead=barmerge.lookahead_on` is crucial for centered TMA calculations, but implies repainting.
//[o_tf, h_tf, l_tf, c_tf] = request.security(syminfo.tickerid, actual_tf, [open, high, low, close], lookahead=barmerge.lookahead_on)
// Get the price series for the selected Price Mode
//float current_price_tf = f_get_price_series(i_price, o_tf, h_tf, l_tf, c_tf)
// Declare the var variables to hold TMA and band values
//var float tmBuffer_val = na
//var float upBuffer_val = na
//var float dnBuffer_val = na
// Calculate TMA and Bands and assign to the var variables
// Note: Due to the centered nature of the MQL4 TMA, this will repaint.
//[tmBuffer_val, upBuffer_val, dnBuffer_val] = f_calculate_tma(current_price_tf, i_halfLength, i_bandsDeviations)
// --- Plotting ---
//plot(tmBuffer_val, title="TMA", color=color.new(color.gray, 0), linewidth=2, style=plot.style_line)
//plot(upBuffer_val, title="Upper Band", color=color.new(color.red, 0), linewidth=1, style=plot.style_line)
//plot(dnBuffer_val, title="Lower Band", color=color.new(color.green, 0), linewidth=1, style=plot.style_line)
// --- Arrows ---
// MQL4 arrows conditions use `i+1` (previous bar on current chart) and `i` (current bar on current chart).
// We need to use `[1]` for previous bar values and `[0]` (or no index) for current bar values.
// Ensure we have enough historical data for these lookbacks.
// Values from the previous bar on the requested timeframe (tf)
// These come from the `request.security` calls, so they are already aligned to `actual_tf`.
//float prev_high_tf = h_tf[1]
//float prev_low_tf = l_tf[1]
//float prev_close_tf = c_tf[1]
//float prev_open_tf = o_tf[1]
//float prev_upBuffer = upBuffer_val[1] // Get previous value of the var series
//float prev_dnBuffer = dnBuffer_val[1] // Get previous value of the var series
// ATR calculation for arrow offset. MQL4 used iATR(NULL,0,20,i) which usually means the current chart's ATR.
// If the selected timeframe `actual_tf` is different from the chart's timeframe,
// using `ta.atr` directly would compute ATR on the chart's timeframe.
// To compute ATR on `actual_tf`, we must request its OHLC series *again* for `ta.atr`.
//float atr_val = ta.atr(20) // Use chart's ATR for simplicity and typical usage.
// If actual_tf's ATR is needed:
// [o_atr_tf, h_atr_tf, l_atr_tf, c_atr_tf] = request.security(syminfo.tickerid, actual_tf, [open, high, low, close], lookahead=barmerge.lookahead_on)
// float atr_val = ta.atr(20, high=h_atr_tf, low=l_atr_tf, close=c_atr_tf)
// Corrected Arrow Plotting Logic
//bool up_arrow_fire = prev_high_tf > prev_upBuffer and prev_close_tf > prev_open_tf and c_tf[0] < o_tf[0]
//plotshape(up_arrow_fire ? h_tf[0] + atr_val : na,
// title="Up Arrow", location=location.abovebar, color=color.new(color.blue, 0), style=shape.arrowup, size=size.small)
//bool dn_arrow_fire = prev_low_tf < prev_dnBuffer and prev_close_tf < prev_open_tf and c_tf[0] > o_tf[0]
//plotshape(dn_arrow_fire ? l_tf[0] - atr_val : na,
// title="Down Arrow", location=location.belowbar, color=color.new(color.red, 0), style=shape.arrowdown, size=size.small)