Pine Script indicator reference

CT Moving Average Crossover: solve for equality, then check the crossing

CT MAC combines nine moving averages with five selected pairs. For each pair, it calculates a candidate current source value that would make the two averages equal. This is a conditional calculation using existing history, not a projection of a future market price.

D(p) = MA_A(p) − MA_B(p)
slope = D(1) − D(0)
equality price = −D(0) / slope, when defined
Inspect the complete script

One chart timeframe, nine calculations

The current Pineify configuration offers SMA, EMA, RMA, WMA, HMA and LSMA, with type codes 1 through 6. Each average has its own length and type, but all use the same source series and chart timeframe. Different lengths are not independent datasets or separate timeframe requests.

Defaults in the displayed call
MALengthTypeVisible in this example
110SMANo
250SMANo
3200SMANo
45EMAOrange
521EMAYellow
655EMAGreen
789EMANo
8200EMABlue
9377EMANo

Pairs 1 through 5 select MA 1/2, 2/3, 4/5, 5/6 and 6/8. Pair 5 is EMA 55 versus EMA 200, not EMA 89 versus EMA 200. The smallest default length is MA 4 at 5 bars; the numbered groups are not ordered from fastest to slowest.

The Visual Editor also has line visibility and fill-pair settings. Those are generation controls. The copied script below has fixed arguments, four MA plots and one equality-price plot, with no Inputs tab settings or fills. A fill only depicts a distance between lines; it does not measure independent buying pressure.

A two-SMA example with an exact answer

Take hypothetical previous closes 104, 102 and 100, oldest to newest. Replace only the current close with p. The current two-bar SMA is (100 + p)/2 and the current three-bar SMA is (102 + 100 + p)/3. Their difference is (p − 104)/6, so equality occurs at 104.

Current closeSMA 2SMA 3Ordering
103101.5101.666667SMA 2 below SMA 3
104102102Equal: no strict cross above
105102.5102.333333SMA 2 above SMA 3

The previous two-bar SMA was 101 and the previous three-bar SMA was 102. A confirmed current close of 105 therefore changes the ordering from below to above. A current close of 104 only makes the lines equal. These inputs demonstrate the formula; they are not a reported trade.

Reverse the pair order and the same root remains 104, but the difference slope becomes −1/6. Above 104, MA A is now below MA B. The shortcut “price above the root means A above B” is valid only with a positive slope. If both averages were already ordered A above B on the previous bar, being above again would be a state, not a new crossing.

What the root does and does not mean

The helper evaluates the current-bar difference at source values 0 and 1. For an affine dependence on that source, two evaluations identify the constant term and slope. It solves the equality analytically from those terms; it does not search for an optimal price or estimate the chance of reaching it.

  • The helper accepts a root only when the absolute slope exceeds 0.0000001 and the result is positive. Otherwise it returns 0. Equal slopes can mean no solution, or equality at every price. The same sentinel covers several cases.
  • A nonpositive source or a valid negative root is not supported by that positive-root filter. Zero should not be read as a price target. The example hides nonpositive pair-3 values.
  • Historical data must be ready. SMA and LSMA require their windows; EMA/RMA use prior recursive states, while some helper branches replace missing values. An early output is not evidence of complete initialization.
  • The HMA candidate helper explicitly floors half-length and square-root length, while the displayed MA uses ta.hma. Verify rounding and numerical parity for the chosen length before treating a candidate root as an exact intersection with that built-in line. This page does not establish parity for every type, length and startup condition.
  • The calculation substitutes the source value, not necessarily the close. If the source is HLC3, a candidate HLC3 value is not directly a closing-price threshold; changing high and low can also change HLC3.

The copied default uses only SMA and EMA. Even then, a floating-point root deserves a tolerance check against both actual MA values. Near-equal slopes can magnify small numerical differences. None of the five outputs predicts a future close or confirms a trend reversal.

Complete Pine Script v6

The calculation helper is included in full from Pineify's current implementation. The call computes all fourteen outputs, and the plots use the returned names. Pair 3 is shown as a changing series, not a permanently fixed horizontal level. There are no alert conditions, entry rules or strategy orders.

Pine Script v6
// This source code is subject to the terms of the Mozilla Public License 2.0 at https://mozilla.org/MPL/2.0/
// © Pineify
//@version=6
indicator("CT Moving Average Crossover", overlay=true)

ctmac_sma_at_price(series float source, simple int length, series float price) =>
    if length <= 1
        price
    else
        (math.sum(source[1], length - 1) + price) / length

ctmac_exp_at_price(series float source, simple int length, series float price, simple bool isRma) =>
    alpha = isRma ? 1.0 / length : 2.0 / (length + 1)
    previous = isRma ? ta.rma(source, length)[1] : ta.ema(source, length)[1]
    alpha * price + (1.0 - alpha) * nz(previous, price)

ctmac_wma_at_price(series float source, simple int length, series float price) =>
    if length <= 1
        price
    else
        previousWeight = (length - 1) * length / 2.0
        totalWeight = length * (length + 1) / 2.0
        previousWma = ta.wma(source[1], length - 1)
        (nz(previousWma) * previousWeight + price * length) / totalWeight

ctmac_lsma_at_price(series float source, simple int length, series float price) =>
    if length <= 1
        price
    else
        sumX = length * (length - 1) / 2.0
        sumX2 = length * (length - 1) * (2 * length - 1) / 6.0
        sumY = price
        sumXY = price * (length - 1)
        for i = 1 to length - 1
            weight = length - 1 - i
            value = source[i]
            sumY := sumY + value
            sumXY := sumXY + weight * value
        denominator = length * sumX2 - sumX * sumX
        slope = denominator != 0 ? (length * sumXY - sumX * sumY) / denominator : 0.0
        intercept = (sumY - slope * sumX) / length
        intercept + slope * (length - 1)

ctmac_hma_at_price(series float source, simple int length, series float price) =>
    if length <= 1
        price
    else
        halfLength = math.max(1, int(math.floor(length / 2.0)))
        sqrtLength = math.max(1, int(math.floor(math.sqrt(length))))
        currentRaw = 2.0 * ctmac_wma_at_price(source, halfLength, price) - ctmac_wma_at_price(source, length, price)
        if sqrtLength <= 1
            currentRaw
        else
            raw = 2.0 * ta.wma(source, halfLength) - ta.wma(source, length)
            previousWeight = (sqrtLength - 1) * sqrtLength / 2.0
            totalWeight = sqrtLength * (sqrtLength + 1) / 2.0
            previousRawWma = ta.wma(raw[1], sqrtLength - 1)
            (nz(previousRawWma) * previousWeight + currentRaw * sqrtLength) / totalWeight

ctmac_clamp_type(simple int maType) =>
    math.min(6, math.max(1, maType))

ctmac_clamp_index(simple int maIndex) =>
    math.min(9, math.max(1, maIndex))

ctmac_ma(series float source, simple int length, simple int maType) =>
    safeLength = math.max(length, 1)
    safeType = ctmac_clamp_type(maType)
    switch safeType
        1 => ta.sma(source, safeLength)
        2 => ta.ema(source, safeLength)
        3 => ta.rma(source, safeLength)
        4 => ta.wma(source, safeLength)
        5 => ta.hma(source, safeLength)
        6 => ta.linreg(source, safeLength, 0)
        => ta.sma(source, safeLength)

ctmac_ma_at_price(series float source, simple int length, simple int maType, series float price) =>
    safeLength = math.max(length, 1)
    safeType = ctmac_clamp_type(maType)
    switch safeType
        1 => ctmac_sma_at_price(source, safeLength, price)
        2 => ctmac_exp_at_price(source, safeLength, price, false)
        3 => ctmac_exp_at_price(source, safeLength, price, true)
        4 => ctmac_wma_at_price(source, safeLength, price)
        5 => ctmac_hma_at_price(source, safeLength, price)
        6 => ctmac_lsma_at_price(source, safeLength, price)
        => ctmac_sma_at_price(source, safeLength, price)

ctmac_select_length(simple int maIndex, simple int len1, simple int len2, simple int len3, simple int len4, simple int len5, simple int len6, simple int len7, simple int len8, simple int len9) =>
    safeIndex = ctmac_clamp_index(maIndex)
    switch safeIndex
        1 => len1
        2 => len2
        3 => len3
        4 => len4
        5 => len5
        6 => len6
        7 => len7
        8 => len8
        => len9

ctmac_select_type(simple int maIndex, simple int type1, simple int type2, simple int type3, simple int type4, simple int type5, simple int type6, simple int type7, simple int type8, simple int type9) =>
    safeIndex = ctmac_clamp_index(maIndex)
    selectedType = switch safeIndex
        1 => type1
        2 => type2
        3 => type3
        4 => type4
        5 => type5
        6 => type6
        7 => type7
        8 => type8
        => type9
    ctmac_clamp_type(selectedType)

ctmac_cross_price(series float source, simple int maIndex1, simple int maIndex2, simple int len1, simple int len2, simple int len3, simple int len4, simple int len5, simple int len6, simple int len7, simple int len8, simple int len9, simple int type1, simple int type2, simple int type3, simple int type4, simple int type5, simple int type6, simple int type7, simple int type8, simple int type9) =>
    selectedLen1 = ctmac_select_length(maIndex1, len1, len2, len3, len4, len5, len6, len7, len8, len9)
    selectedLen2 = ctmac_select_length(maIndex2, len1, len2, len3, len4, len5, len6, len7, len8, len9)
    selectedType1 = ctmac_select_type(maIndex1, type1, type2, type3, type4, type5, type6, type7, type8, type9)
    selectedType2 = ctmac_select_type(maIndex2, type1, type2, type3, type4, type5, type6, type7, type8, type9)
    diffZero = ctmac_ma_at_price(source, selectedLen1, selectedType1, 0.0) - ctmac_ma_at_price(source, selectedLen2, selectedType2, 0.0)
    diffOne = ctmac_ma_at_price(source, selectedLen1, selectedType1, 1.0) - ctmac_ma_at_price(source, selectedLen2, selectedType2, 1.0)
    slope = diffOne - diffZero
    crossPrice = math.abs(slope) > 0.0000001 ? -diffZero / slope : 0.0
    crossPrice > 0 ? crossPrice : 0.0

p_ta_ct_moving_average_crossover(series float source, simple int ma1Length, simple int ma1Type, simple int ma2Length, simple int ma2Type, simple int ma3Length, simple int ma3Type, simple int ma4Length, simple int ma4Type, simple int ma5Length, simple int ma5Type, simple int ma6Length, simple int ma6Type, simple int ma7Length, simple int ma7Type, simple int ma8Length, simple int ma8Type, simple int ma9Length, simple int ma9Type, simple int cross1Ma1, simple int cross1Ma2, simple int cross2Ma1, simple int cross2Ma2, simple int cross3Ma1, simple int cross3Ma2, simple int cross4Ma1, simple int cross4Ma2, simple int cross5Ma1, simple int cross5Ma2) =>
    ma1 = ctmac_ma(source, ma1Length, ma1Type)
    ma2 = ctmac_ma(source, ma2Length, ma2Type)
    ma3 = ctmac_ma(source, ma3Length, ma3Type)
    ma4 = ctmac_ma(source, ma4Length, ma4Type)
    ma5 = ctmac_ma(source, ma5Length, ma5Type)
    ma6 = ctmac_ma(source, ma6Length, ma6Type)
    ma7 = ctmac_ma(source, ma7Length, ma7Type)
    ma8 = ctmac_ma(source, ma8Length, ma8Type)
    ma9 = ctmac_ma(source, ma9Length, ma9Type)
    cross1 = ctmac_cross_price(source, cross1Ma1, cross1Ma2, ma1Length, ma2Length, ma3Length, ma4Length, ma5Length, ma6Length, ma7Length, ma8Length, ma9Length, ma1Type, ma2Type, ma3Type, ma4Type, ma5Type, ma6Type, ma7Type, ma8Type, ma9Type)
    cross2 = ctmac_cross_price(source, cross2Ma1, cross2Ma2, ma1Length, ma2Length, ma3Length, ma4Length, ma5Length, ma6Length, ma7Length, ma8Length, ma9Length, ma1Type, ma2Type, ma3Type, ma4Type, ma5Type, ma6Type, ma7Type, ma8Type, ma9Type)
    cross3 = ctmac_cross_price(source, cross3Ma1, cross3Ma2, ma1Length, ma2Length, ma3Length, ma4Length, ma5Length, ma6Length, ma7Length, ma8Length, ma9Length, ma1Type, ma2Type, ma3Type, ma4Type, ma5Type, ma6Type, ma7Type, ma8Type, ma9Type)
    cross4 = ctmac_cross_price(source, cross4Ma1, cross4Ma2, ma1Length, ma2Length, ma3Length, ma4Length, ma5Length, ma6Length, ma7Length, ma8Length, ma9Length, ma1Type, ma2Type, ma3Type, ma4Type, ma5Type, ma6Type, ma7Type, ma8Type, ma9Type)
    cross5 = ctmac_cross_price(source, cross5Ma1, cross5Ma2, ma1Length, ma2Length, ma3Length, ma4Length, ma5Length, ma6Length, ma7Length, ma8Length, ma9Length, ma1Type, ma2Type, ma3Type, ma4Type, ma5Type, ma6Type, ma7Type, ma8Type, ma9Type)
    [ma1, ma2, ma3, ma4, ma5, ma6, ma7, ma8, ma9, cross1, cross2, cross3, cross4, cross5]

[ma1, ma2, ma3, ma4, ma5, ma6, ma7, ma8, ma9, cross1, cross2, cross3, cross4, cross5] = p_ta_ct_moving_average_crossover(close, 10, 1, 50, 1, 200, 1, 5, 2, 21, 2, 55, 2, 89, 2, 200, 2, 377, 2, 1, 2, 2, 3, 4, 5, 5, 6, 6, 8)
plot(ma4, title="MA 4: EMA 5", color=color.orange, linewidth=2)
plot(ma5, title="MA 5: EMA 21", color=color.yellow, linewidth=2)
plot(ma6, title="MA 6: EMA 55", color=color.green, linewidth=2)
plot(ma8, title="MA 8: EMA 200", color=color.blue, linewidth=2)
plot(cross3 > 0 ? cross3 : na, title="Pair 3 equality price", color=color.gray, style=plot.style_linebr)

The complete displayed source passed Pineify's syntax checker on October 20, 2026. That confirms the syntax check only, not chart execution, all-type numerical parity or trading results. Open the Pine Editor using the Pine icon on the right toolbar, then compare completed-bar values with an independent calculation.

Existing CT Moving Average Crossover illustration showing moving-average overlays

This retained illustration is contextual. It does not verify the numerical example, the revised plots or a trading outcome.

Confirmation belongs to the actual event

For a cross above, require current MA A greater than current MA B and previous MA A less than or equal to previous MA B. Both pairs of values must be available. If the rule requires a confirmed chart bar, evaluate that event on the bar's closing update. An intrabar touch of the candidate price may finish on the other side.

Comparing the live current close with the previous bar's root does not confirm the previous bar. It mixes two different calculation times and still changes with the live close. A threshold computed for one bar should not be reused indefinitely in a static price alert: the history, recursive states and rolling windows advance on the next bar.

Waiting for a bar close removes the current bar's remaining price updates from that decision. It does not establish profitable signals or eliminate data revisions. Multiple pairs also share prices and averages; agreement is not proof of statistically independent confirmation.

Specify a test before adding orders

  1. Choose a pair, source, lengths, MA types, symbol, venue, session and chart timeframe. Start with completed data and record the loaded history.
  2. Compare each candidate helper evaluated at the actual source with the corresponding MA output. Inspect warmup, flat prices, identical pairs and near-zero slopes separately.
  3. Reproduce the 104/102/100 SMA example. Check equality at 104 and the sign on both sides; swap the pair and confirm that the inequality reverses.
  4. Define whether an event is an MA crossing, a price touch, or proximity to a root. These are different rules. Any volume, RSI or ADX filter needs an explicit formula and a separate comparison without the filter.
  5. For a strategy, define exits, fill timing and costs before evaluating it on unused data. For example, a planned entry at 105 with a stop at 103 risks 2 per unit; entry at 106 after a gap risks 3 to the same reference, and a stop can itself fill beyond 103.

No default timeframe, pair or filter has an established win rate here. A 377-bar EMA uses recursive history rather than a hard 377-bar cutoff, and 377 five-minute bars do not imply 31 consecutive clock hours when sessions contain breaks.

Adapt the display or event rules

The Pine Script AI Coding Agent can help draft explicit inputs and diagnostics. A useful request names the desired pair, distinguishes zero sentinels from valid values and asks for actual MA confirmation. Review generated source and chart behavior before adding strategy orders.

Keep the CT MAC helper. Expose MA 4 and MA 5 lengths as inputs, show their values and difference in the Data Window, and label an unavailable or degenerate equality price separately. Add a closed-bar cross-above alert using the actual MA series, with a valid previous pair. Do not infer a win rate or place orders.
Open Pine Script AI Coding Agent

CT MAC questions

It solves for the current source value at which two moving averages are equal, holding their historical inputs fixed. With close as source, this is a candidate current closing price. Equality alone is not a crossover event or a forecast that price will reach the level.

No. The direction depends on the slope of MA A minus MA B as the current source changes. A positive slope makes A greater than B above the root; a negative slope reverses that inequality. A crossover also needs the previous ordering of the two actual averages.

The helper returns zero for a near-zero difference slope, a nonpositive root or unavailable arithmetic. This is a sentinel, not a tradable zero-price target. Identical averages can be equal at every candidate price and have no unique root.

No. This example calculates every average on the current chart timeframe. A 200-bar EMA on a five-minute chart is not a daily 200-bar EMA. Recursive EMA weights also extend beyond the nominal length.

The example hardcodes close, nine lengths and five pairs. It has no input controls. It plots EMA 5, 21, 55 and 200 plus the positive pair-3 equality price. Visual Editor configuration and drawing options are separate from this copied example.

No. It computes and plots series. A separate alert condition must define the actual MA crossing and confirmation policy; a strategy needs order timing, exits and costs. An old equality-price level is not a valid threshold for every subsequent bar.

Sources and related calculations

The nine-MA configuration and candidate-price helper were reviewed against Pineify's implementation on October 20, 2026. The equality and sign examples are direct algebra, not historical market observations.