Sentiment Pulse Indicator For ThinkOrSwim

TRADERSAM

Member
VIP
Been working on a “Sentiment Pulse Oscillator” for a little while now and just wanted to share it with everyone. This indicator combines RSI, CCI, and OBV into one normalized reading between –1 and +1. It smooths the result, shades the background into sentiment zones, and plots vertical lines labeled “BUY” or “SELL” whenever the sentiment crosses into strong bullish or bearish alignment. The idea is to simplify multiple indicators into a single clean view of momentum, trend, and volume pressure. It’s been working well in my testing, but I know it’s far from perfect, so I’d love any suggestions on how to make it more accurate, responsive, or visually clear.

KpGEnIf.png

Here is the code:
Ruby:
declare lower;

# === INPUTS ===
input rsiLength = 14;
input cciLength = 20;
input obvLength = 20;

input cciNormFactor = 200.0;  # Scale for CCI --> [-1,1]
input zScale = 3.0;  # number of stdevs to map OBV z-score

input wRSI = 1.0;
input wCCI = 1.0;
input wOBV = 1.0;

input smoothlength = 3; # Smoothing for final oscillator

input strongThreshold = 0.7;
input mildThreshold = 0.15;

input showcomponents = yes;
input showclouds = yes;

# === Raw component series ===
def rsi = RSI(rsilength);
def cci = CCI(CCIlength);
def obv = onbalancevolume();

# === RSI normalization: 0..100 -> -1..1 ===
def rsiNorm = (rsi - 50) /50;
def rsiNormClip = if rsinorm > 1 then 1 else if rsinorm < -1 then -1 else rsiNorm;

# === CCI normalization: divide by factor then clip ===
def ccinorm = cci / ccinormFactor;
def ccinormclip = if ccinorm > 1 then 1 else if ccinorm < -1 then -1 else ccinorm;

# === OBV normalization via z-score then scale & clip ===
def obvMean = Average(obv, obvlength);
def obvStDev = stdev(obv, obvlength);
def obvz = if obvstdev != 0 then (obv - obvmean) / obvstdev else 0;
def obvnorm = obvz / zscale;
def obvnormclip = if obvnorm > 1 then 1 else if obvnorm < -1 then -1 else obvnorm;

# === Combine with weights ===
def totalweight = wrsi + wcci + wobv;
def sentRaw = (wrsi * rsinormclip + wcci *ccinormclip + wobv * obvnormclip) / totalweight;

# === Smoothlength & final clip to [-1, 1] ===
def sent = expaverage(sentraw, smoothlength);
def sentfinal = if sent > 1 then 1 else if sent < -1 then -1 else sent;

# === Main plot ===
plot sentiment = sentfinal;
sentiment.setDefaultColor(color.cyan);
sentiment.setLineWeight(3);

# === Zero Line ===
plot zeroline = 0;
zeroline.setDefaultColor(color.white);
Zeroline.SetLineWeight(3);

# === Optional component plots ===
plot RSI_Line = if showComponents then rsiNormClip else Double.NaN;
RSI_Line.SetDefaultColor(Color.YELLOW);
RSI_Line.SetLineWeight(1);

plot CCI_Line = if showComponents then cciNormClip else Double.NaN;
CCI_Line.SetDefaultColor(Color.MAGENTA);
CCI_Line.SetLineWeight(1);

plot OBV_Line = if showComponents then obvNormClip else Double.NaN;
OBV_Line.SetDefaultColor(Color.RED);
OBV_Line.SetLineWeight(1);

# === Clouds (5 zones) ===
plot top1 = if showClouds then 1 else Double.NaN;
plot topStrong = if showClouds then strongThreshold else Double.NaN;
plot topMild = if showClouds then mildThreshold else Double.NaN;
plot botMild = if showClouds then -mildThreshold else Double.NaN;
plot botStrong = if showClouds then -strongThreshold else Double.NaN;
plot bot1 = if showClouds then -1 else Double.NaN;

# Strong Bull (0.7..1)
AddCloud(topStrong, top1, Color.DARK_GREEN, Color.DARK_GREEN);

# Mild Bull (0.15..0.7)
AddCloud(topMild, topStrong, Color.LIGHT_GREEN, Color.LIGHT_GREEN);

# Neutral (-0.15..0.15)
AddCloud(botMild, topMild, Color.LIGHT_GRAY, Color.LIGHT_GRAY);

# Mild Bear (-0.7..-0.15)
AddCloud(botStrong, botMild, Color.PINK, Color.PINK);

# Strong Bear (-1..-0.7)
AddCloud(bot1, botStrong, Color.DARK_RED, Color.DARK_RED);

# === Strong alignment cross detection ===
def prevSent = sentFinal[1];
def crossStrongUp = prevSent < strongThreshold and sentFinal >= strongThreshold;
def crossStrongDown = prevSent > -strongThreshold and sentFinal <= -strongThreshold;

# === Vertical lines ===
AddVerticalLine(crossStrongUp, "BUY", Color.GREEN, Curve.SHORT_DASH);
AddVerticalLine(crossStrongDown, "SELL", Color.RED, Curve.SHORT_DASH);

# === Label ===
AddLabel(yes, "Sentiment: " + Round(sentFinal, 2), if sentFinal > 0 then Color.GREEN else if sentFinal < 0 then Color.RED else Color.GRAY);
 
Last edited by a moderator:

Join useThinkScript to post your question to a community of 21,000+ developers and traders.

Similar threads

Not the exact question you're looking for?

Start a new thread and receive assistance from our community.

87k+ Posts
236 Online
Create Post

Similar threads

Similar threads

The Market Trading Game Changer

Join 2,500+ subscribers inside the useThinkScript VIP Membership Club
  • Exclusive indicators
  • Proven strategies & setups
  • Private Discord community
  • ‘Buy The Dip’ signal alerts
  • Exclusive members-only content
  • Add-ons and resources
  • 1 full year of unlimited support

Frequently Asked Questions

What is useThinkScript?

useThinkScript is the #1 community of stock market investors using indicators and other tools to power their trading strategies. Traders of all skill levels use our forums to learn about scripting and indicators, help each other, and discover new ways to gain an edge in the markets.

How do I get started?

We get it. Our forum can be intimidating, if not overwhelming. With thousands of topics, tens of thousands of posts, our community has created an incredibly deep knowledge base for stock traders. No one can ever exhaust every resource provided on our site.

If you are new, or just looking for guidance, here are some helpful links to get you started.

What are the benefits of VIP Membership?
VIP members get exclusive access to these proven and tested premium indicators: Buy the Dip, Advanced Market Moves 2.0, Take Profit, and Volatility Trading Range. In addition, VIP members get access to over 50 VIP-only custom indicators, add-ons, and strategies, private VIP-only forums, private Discord channel to discuss trades and strategies in real-time, customer support, trade alerts, and much more. Learn all about VIP membership here.
How can I access the premium indicators?
To access the premium indicators, which are plug and play ready, sign up for VIP membership here.
Back
Top