Reverse Engineered Composite Index

Viewing 3 posts - 1 through 3 (of 3 total)
  • Author
    Posts
  • #228427 quote
    nils_k
    Participant
    New

    Hi !

    I have a question relating to the reverse engineering of a momentum oscillator. I hope you can help me with that. Thank you in advance.

     

    Goal

    I want to reverse engineer Constance Brown’s Composite Index. That is, I don’t want to know the value of the Composite Index given the current price of the last bar, but I want to know where the price of the current bar would need to go to reach a specific value in the Composite Index (e.g.: Where would the price on the current (i.e., ongoing) bar would need to go so that the Composite Index would reach >= 100 for the current bar).

     

    Background Information

    Constance Brown’s Composite Index is calculated in the following way:

    // Step 1: Create a 9-period momentum indicator of a 14-period RSI.
    
    RSIMO9 = Momentum[9](RSI[14](close))
    
    // Step 2: Create a 3-priod simple moving average of a 3-period RSI.
    
    RSI3 = Average[3](RSI[3](close))
    
    // Step 3: Add the value of the indicators created in Step 1 and 2 together to create the Composite Index.
    
    Composite = RSIMO9 + RSI3

     

    Sketch of a Solution

    I have already found the code for reverse engineering the RSI in the indicator library (Link). I just cannot come up with a solution for applying a similar process for the Composite Index.

     

    Thank you so much for your help.

     

    Regards, Nils

    #260085 quote
    Nicolas
    Keymaster
    Legend

    Hi Nils,

    I’ve been working on something that seemed straightforward at first but turned into a much deeper rabbit hole than I expected, so I figured I’d share the journey in case anyone else wants to try something similar.

    Most of us use oscillators the normal way, price comes in, indicator value comes out. I wanted to flip that around. Specifically for Constance Brown’s Composite Index, I wanted to know: where would the current bar need to close for the Composite to hit a specific value, say 100?

    For those unfamiliar, the Composite is built like this:

    • Take a 14-period RSI, then apply a 9-period Momentum to it
    • Take a 3-period RSI, then smooth it with a 3-period simple moving average
    • Add the two together

    Simple enough to calculate forward. Turns out inverting it is a different story entirely.

    So I thought it would be easy, but there’s a well-known technique for reverse-engineering a plain RSI, originally by Giorgos Siligardos. You unpack the Wilder smoothing one step, do some algebra, and out comes the price. I figured I’d just apply the same idea to the Composite. I was wrong 🙁


    In fact, it’s actually hard!

    Here’s the problem. When you try to invert the Composite, you end up with two RSI calculations that both depend on the same unknown price X:

    • RSI14(X) appears in the Momentum component
    • RSI3(X) appears in the moving average component

    Each of them runs through its own Wilder smoothing chain with a different period. And because they’re both non-linear functions of the same unknown, you can’t separate them algebraically. Every time I tried to isolate X on one side of the equation, I’d end up with a mess of nested fractions that just couldn’t be untangled. There is genuinely no closed-form solution here. I’m not missing a trick, it just doesn’t exist.


    So, instead, since Composite(X) always increases as price increases (higher price → higher RSI14 and RSI3 → higher Composite), the function is strictly monotonic. That means there’s exactly one price that hits any given target, and bisection will always find it.

    The approach is:

    • Freeze the Wilder state from the previous bar (those historical averages are known and fixed)
    • For any trial price, manually compute what RSI14 and RSI3 would be after one Wilder update step
    • Use that to evaluate the full Composite at any hypothetical price
    • Run a bisection loop, halving the search bracket 50 times until the result converges

    Fifty iterations gives precision far beyond anything meaningful for a price level, so convergence is not the issue.


    The part that’s genuinely tricky to communicate on a chart

    This is where it gets interesting, and honestly where I spent most of my time.

    When you ask “where does price need to go to hit 100 on the Composite?”, there’s an implicit assumption that the answer is always some nearby, reachable price. That assumption is often wrong.

    Because RSI14 uses 14-period Wilder smoothing, each new bar only shifts the running average by 1/14th. RSI3 is more reactive at 1/3 per bar, but still bounded between 0 and 100. The consequence is that on any given bar, the Composite has a hard floor and a hard ceiling, determined entirely by the current state of those two Wilder averages. And those bounds are often much tighter than you’d expect.

    If the Composite is sitting at 20 and you ask for a target of 100, that might simply be impossible on the current bar. Not unlikely. Not impractical. Mathematically impossible, no matter how far price moves. The indicator can only move so far in a single bar.

    My first attempt just let the bisection run anyway in those cases. It would find the bracket ceiling and return that as the answer, which on a DAX chart at 24,000 meant numbers like 120,000 showing up on the price axis. Not great.

    The fix was to stop pretending the target is reachable and instead show the closest the Composite can physically get. I evaluate the Composite at ±50% of the previous close (which covers any realistic single-bar move for a liquid instrument) and use those as the practical floor and ceiling. If the target falls outside that range, the indicator plots a dotted grey line at the boundary price instead of a solid white line at the target price. The dotted line is telling you: “your target is out of reach today, and this is as close as the market can get.”

    The visual logic ends up being pretty clean:

    • Solid white line → exact price needed to hit your target
    • Dotted grey line → target is unreachable on this bar, here’s the physical limit
    // Reverse Engineering Constance Brown's Composite Index
    // Returns the price needed to hit CompTarget.
    // If target is out of reach, ClampedPrice shows the nearest practically
    // achievable price boundary, always within a realistic range of current price.
    
    // ---- Parameters ----
    CompTarget  = 100
    RSI14Period = 14
    RSI3Period  = 3
    SMA3Period  = 3
    
    // ---- Wilder coefficients ----
    k14 = 1 / RSI14Period
    k3  = 1 / RSI3Period
    
    // ---- Frozen Wilder state entering the current bar ----
    upOri14      = MAX(0, close - close[1])
    downOri14    = MAX(0, close[1] - close)
    upMA14       = WilderAverage[RSI14Period](upOri14)
    downMA14     = WilderAverage[RSI14Period](downOri14)
    upMA14prev   = upMA14[1]
    downMA14prev = downMA14[1]
    
    upOri3      = MAX(0, close - close[1])
    downOri3    = MAX(0, close[1] - close)
    upMA3       = WilderAverage[RSI3Period](upOri3)
    downMA3     = WilderAverage[RSI3Period](downOri3)
    upMA3prev   = upMA3[1]
    downMA3prev = downMA3[1]
    
    // ---- Fixed historical values ----
    RSI14ago9 = RSI[RSI14Period](close)[9]
    RSI3bar1  = RSI[RSI3Period](close)[1]
    RSI3bar2  = RSI[RSI3Period](close)[2]
    
    // ---- Practical bisection bracket ----
    // close[1] * 0.5 to close[1] * 1.5 covers any realistic single-bar move.
    // CompMin and CompMax are evaluated AT these boundaries, not at theoretical
    // infinity, so clamped prices are always on scale.
    loPrice = close[1] * 0.5
    hiPrice = close[1] * 1.5
    
    // ---- Composite at loPrice ----
    upMoveL14   = MAX(0, loPrice - close[1])
    downMoveL14 = MAX(0, close[1] - loPrice)
    upMAL14     = upMA14prev*(1-k14) + upMoveL14*k14
    downMAL14   = downMA14prev*(1-k14) + downMoveL14*k14
    RSI14L      = 100 * upMAL14 / (upMAL14 + downMAL14)
    upMoveL3    = MAX(0, loPrice - close[1])
    downMoveL3  = MAX(0, close[1] - loPrice)
    upMAL3      = upMA3prev*(1-k3) + upMoveL3*k3
    downMAL3    = downMA3prev*(1-k3) + downMoveL3*k3
    RSI3L       = 100 * upMAL3 / (upMAL3 + downMAL3)
    CompAtLo    = (RSI14L - RSI14ago9) + (RSI3L + RSI3bar1 + RSI3bar2) / 3
    
    // ---- Composite at hiPrice ----
    upMoveH14   = MAX(0, hiPrice - close[1])
    downMoveH14 = MAX(0, close[1] - hiPrice)
    upMAH14     = upMA14prev*(1-k14) + upMoveH14*k14
    downMAH14   = downMA14prev*(1-k14) + downMoveH14*k14
    RSI14H      = 100 * upMAH14 / (upMAH14 + downMAH14)
    upMoveH3    = MAX(0, hiPrice - close[1])
    downMoveH3  = MAX(0, close[1] - hiPrice)
    upMAH3      = upMA3prev*(1-k3) + upMoveH3*k3
    downMAH3    = downMA3prev*(1-k3) + downMoveH3*k3
    RSI3H       = 100 * upMAH3 / (upMAH3 + downMAH3)
    CompAtHi    = (RSI14H - RSI14ago9) + (RSI3H + RSI3bar1 + RSI3bar2) / 3
    
    // ---- Seed CompLoB for bisection ----
    CompLoB = CompAtLo
    
    // ---- Route: clamp or bisect ----
    ExactPrice   = undefined
    ClampedPrice = undefined
    
    IF CompTarget <= CompAtLo THEN
       ClampedPrice = loPrice
       
    ELSIF CompTarget >= CompAtHi THEN
       ClampedPrice = hiPrice
       
    ELSE
       loB   = loPrice
       hiB   = hiPrice
       midB  = close
       iterB = 0
       
       WHILE iterB < 50 DO
          midB = (loB + hiB) / 2
          
          upMoveMid14   = MAX(0, midB - close[1])
          downMoveMid14 = MAX(0, close[1] - midB)
          upMAMid14     = upMA14prev*(1-k14) + upMoveMid14*k14
          downMAMid14   = downMA14prev*(1-k14) + downMoveMid14*k14
          RSI14Mid      = 100 * upMAMid14 / (upMAMid14 + downMAMid14)
          
          upMoveMid3    = MAX(0, midB - close[1])
          downMoveMid3  = MAX(0, close[1] - midB)
          upMAMid3      = upMA3prev*(1-k3) + upMoveMid3*k3
          downMAMid3    = downMA3prev*(1-k3) + downMoveMid3*k3
          RSI3Mid       = 100 * upMAMid3 / (upMAMid3 + downMAMid3)
          
          compMidB = (RSI14Mid - RSI14ago9) + (RSI3Mid + RSI3bar1 + RSI3bar2) / 3
          
          IF (compMidB - CompTarget) * (CompLoB - CompTarget) <= 0 THEN
             hiB = midB
          ELSE
             loB     = midB
             CompLoB = compMidB
          ENDIF
          
          iterB = iterB + 1
       WEND
       
       ExactPrice = midB
    ENDIF
    
    // ---- Live Composite for reference / verification ----
    RSI14now  = RSI[RSI14Period](close)
    RSI3now   = RSI[RSI3Period](close)
    RSI3sma   = Average[SMA3Period](RSI3now)
    RSIMO9now = RSI14now - RSI14ago9
    CompNow   = RSIMO9now + RSI3sma
    
    RETURN ExactPrice AS "Target Price (exact)" COLOURED(255,255,255), ClampedPrice AS "Target Price (clamped)" STYLE(dottedline) COLOURED(128,128,128), CompNow AS "Composite Now"
    


    robertogozzi thanked this post
    composite-index-reversed-engineered.png composite-index-reversed-engineered.png
    #260566 quote
    PeterSt
    Participant
    Master

    Hi Nicolas,


    You changed personality (this appears to be so in all your recent posts).

    😉



    Btw hint : If no closed form exists then loop till you drop. You seem to say the same, but the relation / context was dropped. Or maybe as a human being I cannot follow.



    Let’s see what you can do with this :


    The clean untangling is this:

    The text is directionally right about using numerical inversion, but it overstates two things:

    1. “There is no closed-form solution” is probably not true.
    2. For one bar ahead, both RSI values depend on the same unknown close X, but each RSI is only a piecewise rational function of X.
    3. Let:
    D = X - PreviousClose
    
    1. For D >= 0, current gain is D, current loss is 0.
    2. For D < 0, current gain is 0, current loss is -D.
    3. So RSI14 and RSI3 each become a simple rational expression in D. The Composite Index becomes:
    Constant + RSI14(D) + RSI3(D) / 3
    
    1. Setting that equal to a target gives, per side of PreviousClose, a quadratic equation, not an impossible algebraic mess.
    2. So the better statement is:
    It does not reduce to the simple Siligardos-style inverse RSI formula, but it can still be solved algebraically as a piecewise quadratic. Numerically solving it is easier and safer.
    1. “The Composite can only move so far in one bar no matter how far price moves” is only partly true.
    2. RSI itself is bounded between 0 and 100, so yes, the Composite has an upper and lower theoretical range.
    3. But the reason is not that Wilder smoothing only allows a 1/14 or 1/3 movement. A huge enough price move can dominate the previous Wilder average and push the current RSI arbitrarily close to 100 or 0.
    4. The true theoretical limits are roughly:
    UpperLimit = 100 - RSI14_9BarsAgo + (100 + PreviousRSI3_1 + PreviousRSI3_2) / 3
    
    LowerLimit = 0 - RSI14_9BarsAgo + (0 + PreviousRSI3_1 + PreviousRSI3_2) / 3
    
    1. So the target can indeed be unreachable, but because of the bounded RSI components and fixed historical terms, not because a single bar cannot strongly affect Wilder smoothing.

    The practical charting solution is reasonable:

    Solid line  = target is reachable within the chosen price bracket
    Dotted line = target is outside the chosen practical bracket
    

    But I would not call the ±50% bracket a “physical limit.” It is a practical plotting limit. The mathematical limit is the RSI asymptote as price goes toward infinity or minus infinity.

    A cleaner version of the core idea would be:

    The Composite Index can be evaluated for any hypothetical current close by freezing all previous RSI/Wilder state and recalculating only the current bar. Because the result is monotonic in price under normal conditions, bisection is a robust way to find the price that would produce a chosen Composite value. However, some targets may be outside the Composite’s theoretical or practical range for the current bar, so the script should first check whether the target is reachable inside the chosen bracket. If not, it should plot the nearest reachable boundary instead of returning a meaningless extreme price.




Viewing 3 posts - 1 through 3 (of 3 total)
  • You must be logged in to reply to this topic.

Reverse Engineered Composite Index


ProBuilder: Indicators & Custom Tools

New Reply
Author
author-avatar
nils_k @nils_k Participant
Summary

This topic contains 2 replies,
has 3 voices, and was last updated by PeterSt
1 month ago.

Topic Details
Forum: ProBuilder: Indicators & Custom Tools
Language: English
Started: 02/20/2024
Status: Active
Attachments: 1 files
Logo Logo
Loading...