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="Corona Trend Vigor v2.1", shorttitle="CTV v2.1", overlay=false,
format=format.price, precision=2)
// --- Inputs ---
var TimeFrame = input.timeframe("0", "Time Frame") // "0" means current chart's timeframe
var PriceMode = input.int(4, "Price Mode (0-10)", minval=0, maxval=10)
var LineColor = input.color(color.blue, "Line Color")
var FuzzR = input.int(0, "Fuzz Red (0-255)", minval=0, maxval=255)
var FuzzG = input.int(0, "Fuzz Green (0-255)", minval=0, maxval=255)
var FuzzB = input.int(255, "Fuzz Blue (0-255)", minval=0, maxval=255)
var VisualMode = input.int(0, "Visual Mode (0-TV+Corona, 1-TV, 2-Corona)", minval=0, maxval=2)
var CoronaBars = input.int(1000, "Corona Bars (0 for all)", minval=0)
// --- Constants ---
float pi = math.atan(1) * 4
// --- Global Variables (using var for persistence across bars) ---
var float[] Q = array.new_float(60, 0.0)
var float[] I = array.new_float(60, 0.0)
var float[] Real = array.new_float(60, 0.0)
var float[] Imag = array.new_float(60, 0.0)
var float[] Ampl = array.new_float(60, 0.0)
var float[] DB = array.new_float(60, 0.0)
var float[] OldI = array.new_float(60, 0.0)
var float[] OlderI = array.new_float(60, 0.0)
var float[] OldQ = array.new_float(60, 0.0)
var float[] OlderQ = array.new_float(60, 0.0)
var float[] OldReal = array.new_float(60, 0.0)
var float[] OlderReal = array.new_float(60, 0.0)
var float[] OldImag = array.new_float(60, 0.0)
var float[] OlderImag = array.new_float(60, 0.0)
var float[] OldDB = array.new_float(60, 0.0)
var float[] Raster = array.new_float(51, 0.0)
var float[] OldRaster = array.new_float(51, 0.0)
var float FuzzWidth = 0.4 // Global constant
var int mcnt_bars = 0
var int per = 30 // MQL4 default
// --- Helper Functions ---
// Custom Heiken Ashi calculation
f_heiken_ashi(tf, price_type, bar_index_offset) =>
// Determine the actual timeframe string for request.security
string actual_tf = tf == "0" ? timeframe.period : tf
// Get current bar's OHLC data for the specified timeframe using request.security
[o, h, l, c] = request.security(syminfo.tickerid, actual_tf, [open, high, low, close], lookahead=barmerge.lookahead_on)
// Using `var` to store HA values persistently, similar to MQL4 arrays
var float ha_close = 0.0
var float ha_open = 0.0
var float ha_high = 0.0
var float ha_low = 0.0
// HA Close
current_ha_close = (o + h + l + c) / 4.0
// HA Open
// For the very first bar, ha_open[1] and ha_close[1] will be na, so use nz()
current_ha_open = bar_index == 0 ? (o + c) / 2.0 : (nz(ha_open[1]) + nz(ha_close[1])) / 2.0
// HA High
current_ha_high = math.max(h, current_ha_open, current_ha_close)
// HA Low
current_ha_low = math.min(l, current_ha_open, current_ha_close)
// Update `var` HA values for next bar's calculation
ha_close := current_ha_close
ha_open := current_ha_open
ha_high := current_ha_high
ha_low := current_ha_low
// Return the requested HA price type
float ha_val = 0.0
if price_type == 0 // haClose
ha_val := current_ha_close
else if price_type == 1 // haOpen
ha_val := current_ha_open
else if price_type == 2 // haHigh
ha_val := current_ha_high
else if price_type == 3 // haLow
ha_val := current_ha_low
ha_val
// Custom Median function (for series)
// Now accepts a 'series float' and creates a temporary array from its history.
f_median(input_series, count) =>
// Return 'na' if there aren't enough bars for the 'count' period
if bar_index < count - 1
na
else
// Create a temporary array to hold the last 'count' values from the series
temp_arr = array.new_float(count)
// Populate the array with values from the series_input
// input_series[0] is current, input_series[1] is 1 bar ago, etc.
for i = 0 to count - 1
array.set(temp_arr, i, input_series[i])
array.sort(temp_arr)
float median_val = 0.0
int num = math.round(float(count - 1) / 2.0)
if (count % 2) > 0 // Odd number of elements
median_val := array.get(temp_arr, num)
else // Even number of elements
median_val := (array.get(temp_arr, num) + array.get(temp_arr, num + 1)) / 2.0
median_val
// Function to get price based on PriceMode
f_get_price_series(mode, tf) =>
// Determine the actual timeframe string for request.security
string actual_tf = tf == "0" ? timeframe.period : tf
// Request OHLC data from the specified timeframe unconditionally.
[o, h, l, c] = request.security(syminfo.tickerid, actual_tf, [open, high, low, close], lookahead=barmerge.lookahead_on)
// Calculate Heiken Ashi components unconditionally
float ha_close_val = f_heiken_ashi(actual_tf, 0, 0)
float ha_open_val = f_heiken_ashi(actual_tf, 1, 0)
float ha_high_val = f_heiken_ashi(actual_tf, 2, 0)
float ha_low_val = f_heiken_ashi(actual_tf, 3, 0)
// Use a switch statement to select the correct price series based on `mode`
float price_series_val = switch mode
0 => c // Close
1 => o // Open
2 => h // High
3 => l // Low
4 => (h + l) / 2 // Median Price (H+L)/2
5 => (h + l + c) / 3 // Typical Price (H+L+C)/3
6 => (h + l + c * 2) / 4 // Weighted Close (H+L+C*2)/4
7 => ha_close_val // Heiken Ashi Close
8 => ha_open_val // Heiken Ashi Open
9 => ha_high_val // Heiken Ashi High
10 => ha_low_val // Heiken Ashi Low
=> c // Default case (should not be reached due to input.int min/max)
price_series_val
// --- Indicator Buffers (Pine Script equivalent: series variables) ---
var float TV_line = na // This will hold the main Trend Vigor line
// --- Main Calculation ---
// MQL4 `start()` function logic converted to Pine Script's bar-by-bar execution.
// Get price for the current timeframe and PriceMode
float current_price = f_get_price_series(PriceMode, TimeFrame)
float prev_price = f_get_price_series(PriceMode, TimeFrame)[1] // Price of previous bar on selected TF
// --- Calculate HP and SmoothHP ---
var float HP = 0.0 // MQL4: HP[y]
var float SmoothHP = 0.0 // MQL4: SmoothHP[y]
var float prev_HP = 0.0 // MQL4: HP[y-1]
var float prev_SmoothHP = 0.0 // MQL4: SmoothHP[y-1]
var float prev2_HP = 0.0
var float prev3_HP = 0.0
var float prev4_HP = 0.0
var float prev5_HP = 0.0
alpha1 = (1 - math.sin(2 * pi / 30)) / math.cos(2 * pi / 30)
// HP[y] = 0.5*(1 + alpha1)*(price[y] - price[y-1]) + alpha1*HP[y-1];
current_HP = 0.5 * (1 + alpha1) * (current_price - prev_price) + alpha1 * nz(prev_HP)
// SmoothHP[y] = (HP[y] + 2.0*HP[y-1] + 3.0*HP[y-2] + 3.0*HP[y-3] + 2.0*HP[y-4] + HP[y-5]) / 12.0;
current_SmoothHP = (current_HP + 2.0 * nz(prev_HP) + 3.0 * nz(prev2_HP) + 3.0 * nz(prev3_HP) + 2.0 * nz(prev4_HP) + nz(prev5_HP)) / 12.0
// MQL4 conditional: if(y < 6) SmoothHP[y] = price[y] - price[y-1]; if(y == 0) SmoothHP[y] = 0;
// Pine Script's `bar_index` handles initial bars:
if bar_index < 5 // Equivalent to y < 6 (as array indices are 0-based and [0] is current)
current_SmoothHP := current_price - prev_price
if bar_index == 0
current_SmoothHP := 0.0
// Update previous HP and SmoothHP for next bar's calculation
prev5_HP := prev4_HP
prev4_HP := prev3_HP
prev3_HP := prev2_HP
prev2_HP := prev_HP
prev_HP := current_HP
SmoothHP := current_SmoothHP // Assign to persistent SmoothHP variable
// --- Calculate Corona Trend Vigor ---
// MQL4's `y` is essentially `bar_index` in Pine Script.
float delta = -0.015 * (bar_index + 1) + 0.5
if delta < 0.1
delta := 0.1
// Update OldI, OldQ, OldReal, OldImag, OldDB arrays for the current bar
for n = 11 to 59
array.set(OlderI, n, array.get(OldI, n))
array.set(OldI, n, array.get(I, n))
array.set(OlderQ, n, array.get(OldQ, n))
array.set(OldQ, n, array.get(Q, n))
array.set(OlderReal, n, array.get(OldReal, n))
array.set(OldReal, n, array.get(Real, n))
array.set(OlderImag, n, array.get(OldImag, n))
array.set(OldImag, n, array.get(Imag, n))
array.set(OldDB, n, array.get(DB, n))
float beta = math.cos(4 * pi / (n + 1))
float gamma = 1.0 / math.cos(4 * pi * delta / (n + 1))
float alpha = gamma - math.sqrt(gamma * gamma - 1)
// Using `nz()` for previous bar's SmoothHP values as they are calculated on the current bar
float prev_smoothHP_val = nz(SmoothHP[1])
float prev2_smoothHP_val = nz(SmoothHP[2])
array.set(Q, n, ((n + 1) / (4 * pi)) * (SmoothHP - prev_smoothHP_val))
array.set(I, n, SmoothHP)
float val_I = array.get(I, n)
float val_OlderI = array.get(OlderI, n)
float val_OldReal = array.get(OldReal, n)
float val_OlderReal = array.get(OlderReal, n)
float val_Q = array.get(Q, n)
float val_OlderQ = array.get(OlderQ, n)
float val_OldImag = array.get(OldImag, n)
float val_OlderImag = array.get(OlderImag, n)
array.set(Real, n, 0.5 * (1 - alpha) * (val_I - val_OlderI) + beta * (1 + alpha) * val_OldReal - alpha * val_OlderReal)
array.set(Imag, n, 0.5 * (1 - alpha) * (val_Q - val_OlderQ) + beta * (1 + alpha) * val_OldImag - alpha * val_OlderImag)
float real_val = array.get(Real, n)
float imag_val = array.get(Imag, n)
array.set(Ampl, n, real_val * real_val + imag_val * imag_val)
var float MaxAmpl = 0.0
// Find MaxAmpl
if bar_index > 11 // Need enough bars for initial calculations (similar to y > 11 in MQL4)
MaxAmpl := array.get(Ampl, 11)
for n = 11 to 59
if array.get(Ampl, n) > MaxAmpl
MaxAmpl := array.get(Ampl, n)
// Calculate DB values
if bar_index > 11
for n = 11 to 59
float dB_calc = 0.0
if MaxAmpl != 0 and array.get(Ampl, n) / MaxAmpl > 0
dB_calc := -10.0 * math.log10(0.01 / (1 - 0.99 * array.get(Ampl, n) / MaxAmpl))
array.set(DB, n, 0.33 * dB_calc + 0.67 * array.get(OldDB, n))
if array.get(DB, n) > 20
array.set(DB, n, 20.0)
// Calculate Dominant Cycle (DC)
var float DC_val = 0.0 // MQL4: DC[y]
if bar_index > 11 // Needs enough bars for DB calculation
float Num = 0.0
float Denom = 0.0
for n = 11 to 59
if array.get(DB, n) <= 6
Num := Num + (n + 1) * (20 - array.get(DB, n))
Denom := Denom + (20 - array.get(DB, n))
if Denom != 0
DC_val := 0.5 * Num / Denom
else if bar_index > 0
DC_val := nz(DC_val[1]) // Equivalent to DC[y-1]
var float mDomCyc = 0.0 // MQL4: mDomCyc[y]
// We'll use series_DC directly as the input for f_median.
series_DC = DC_val // This remains a series float
// Ensure we have enough bars for the median calculation (current_bar_idx - i should be valid)
if bar_index >= 4 // For 5-period median, we need at least 4 previous bars (0-4)
mDomCyc := f_median(series_DC, 5) // Call f_median with series_DC and count
else
mDomCyc := nz(mDomCyc[1]) // Keep previous value or na if not enough bars
if mDomCyc < 6
mDomCyc := 6.0
// Filter Bandpass component (IP)
var float IP_val = 0.0 // MQL4: IP[y]
var float prev_IP_val = 0.0
var float prev2_IP_val = 0.0
float delta1 = 0.1
float beta1 = math.cos(2 * pi / mDomCyc)
float gamma1 = 1.0 / math.cos(4 * pi * delta1 / mDomCyc)
float alpha2 = gamma1 - math.sqrt(gamma1 * gamma1 - 1)
// IP[y] = 0.5*(1 - alpha2)*(price[y] - price[y-2]) + beta1*(1 + alpha2)*IP[y-1] - alpha2*IP[y-2];
current_IP_calc = 0.5 * (1 - alpha2) * (current_price - nz(current_price[2])) + beta1 * (1 + alpha2) * nz(prev_IP_val) - alpha2 * nz(prev2_IP_val)
// Update previous IP values
prev2_IP_val := prev_IP_val
prev_IP_val := current_IP_calc
IP_val := current_IP_calc
// Quadrature component (Q1)
float Q1 = (mDomCyc / (2 * pi)) * (IP_val - nz(IP_val[1]))
// Pythagorean theorem to establish cycle amplitude (Ampl2)
float Ampl2 = math.sqrt(IP_val * IP_val + Q1 * Q1)
// Trend amplitude (Ratio)
var float Ratio_val = 0.0
int iDomCyc = math.round(mDomCyc)
float trend_val = current_price - nz(current_price[iDomCyc + 1]) // price[y-iDomCyc-1]
if trend_val != 0 and Ampl2 != 0
Ratio_val := 0.33 * trend_val / Ampl2 + 0.67 * nz(Ratio_val[1])
if Ratio_val > 10
Ratio_val := 10.0
if Ratio_val < -10
Ratio_val := -10.0
var float mTV = 0.0 // MQL4: mTV[y]
mTV := 0.05 * (Ratio_val + 10)
// Clamp mTV
if mTV < 0.0
mTV := 0.0
if mTV > 1.0
mTV := 1.0
// Calculate Width for Raster
float Width = 0.01 // Default as per MQL4
if mTV >= 0.3 and mTV < 0.5
Width := mTV - 0.3
else if mTV > 0.5 and mTV <= 0.7
Width := -mTV + 0.7
// Calculate Raster values
for n = 1 to 50
raster_n = 20.0 // Default value
if n < math.round(50 * mTV)
// MQL4: Raster[n] = 0.8*(MathPow(( 20*mTV - 0.4*n)/Width,0.85) + 0.2*OldRaster[n]);
// Avoid division by zero if Width is 0
pow_base = Width != 0 ? (20 * mTV - 0.4 * n) / Width : 0.0 // Handle Width=0
if pow_base < 0
pow_base := 0.0 // MathPow with negative base and non-integer exponent is complex. Clamp to 0 if negative.
raster_n := 0.8 * (math.pow(pow_base, 0.85) + 0.2 * array.get(OldRaster, n))
else if n > math.round(50 * mTV)
// MQL4: Raster[n] = 0.8*(MathPow((-20*mTV + 0.4*n)/Width,0.85) + 0.2*OldRaster[n]);
// Avoid division by zero if Width is 0
pow_base = Width != 0 ? (-20 * mTV + 0.4 * n) / Width : 0.0 // Handle Width=0
if pow_base < 0
pow_base := 0.0 // Clamp to 0 if negative.
raster_n := 0.8 * (math.pow(pow_base, 0.85) + 0.2 * array.get(OldRaster, n))
else if n == math.round(50 * mTV)
raster_n := 0.5 * array.get(OldRaster, n)
if raster_n < 0
raster_n := 0.0
if raster_n > 20 or mTV < 0.3 or mTV > 0.7
raster_n := 20.0
array.set(Raster, n, raster_n)
array.set(OldRaster, n, raster_n) // Update OldRaster for next bar
// --- Plotting Logic ---
// Main Trend Vigor line (TV)
TV_value = 20 * mTV - 10
// Determine the value to plot for TV_line, `na` if not visible
float plot_TV_line = if VisualMode == 0 or VisualMode == 1
TV_value
else
na
plot(plot_TV_line, title="Trend Vigor", color=LineColor, linewidth=2, style=plot.style_line, display=display.pane)
// Corona Visualization (Simplified Plotting)
// As previously discussed, exact replication of MQL4's OBJ_RECTANGLE for 50 items per bar
// in a separate pane is not directly supported by Pine Script.
// This version plots a single 'Corona Intensity' line with dynamic coloring.
// Calculate Corona Intensity values only if needed for plotting
float avg_raster = 0.0
if VisualMode == 0 or VisualMode == 2
for n = 1 to 50
avg_raster := avg_raster + array.get(Raster, n)
avg_raster := avg_raster / 50.0
// Get individual RGB components of LineColor using color.r(), color.g(), color.b() functions
float line_r = color.r(LineColor)
float line_g = color.g(LineColor)
float line_b = color.b(LineColor)
int color1_comp = 0
int color2_comp = 0
int color3_comp = 0
// Replicate MQL4 color calculation logic
if avg_raster <= 10
color1_comp := math.round(line_r + avg_raster * (FuzzR - line_r) / 10.0)
color2_comp := math.round(line_g + avg_raster * (FuzzG - line_g) / 10.0)
color3_comp := math.round(line_b + avg_raster * (FuzzB - line_b) / 10.0)
else // avg_raster > 10
color1_comp := math.round(FuzzR * (2 - avg_raster / 10.0))
color2_comp := math.round(FuzzG * (2 - avg_raster / 10.0))
color3_comp := math.round(FuzzB * (2 - avg_raster / 10.0))
color corona_color = color.rgb(math.max(0, math.min(255, color1_comp)),
math.max(0, math.min(255, color2_comp)),
math.max(0, math.min(255, color3_comp)))
// Determine the value to plot for Corona Intensity, `na` if not visible
float plot_corona_intensity = if VisualMode == 0 or VisualMode == 2
0 // Plotting at 0 for columns, as per original intent
else
na
// Define the color for the Corona Intensity plot. This needs to be a series color.
// Use `na` if the plot is not visible.
color final_corona_color = if VisualMode == 0 or VisualMode == 2
corona_color
else
na
plot(plot_corona_intensity, title="Corona Intensity", color=final_corona_color, style=plot.style_columns, linewidth=1, display=display.pane)
// Plot indicator levels
hline(2, "Level +2", color=color.gray, linestyle=hline.style_dashed)
hline(-2, "Level -2", color=color.gray, linestyle=hline.style_dashed)