Trader Psychology Analysis
Overview
Markets are driven by human psychology as much as fundamentals. Understanding cognitive biases, herding dynamics, and fear/greed cycles allows you to anticipate price movements caused by irrational behaviour. This skill maps behavioural finance theory to quantifiable trading signals.
When to Use
- Modelling retail vs. institutional behaviour from order flow or positioning data
- Building contrarian indicators from extreme sentiment
- Detecting FOMO-driven momentum and FUD-driven capitulation
- Interpreting sentiment extremes as mean-reversion signals
- Explaining anomalous price movements through behavioural lens
Key Cognitive Biases and Market Impact
| Bias |
Description |
Market Effect |
Signal |
| Loss Aversion |
Losses hurt ~2× more than gains |
Reluctance to cut losers → trend prolongation |
Large open loss concentration = reversal risk |
| Recency Bias |
Overweight recent events |
Momentum overshoot |
High recent returns → mean reversion expectation |
| Anchoring |
Over-rely on reference price |
Support/resistance at round numbers |
Volume clusters at .00 prices |
| Herding |
Follow the crowd |
Bubble formation, panics |
Extreme long/short positioning |
| Overconfidence |
Overestimate skill |
Excessive trading, risk-taking |
High retail volume in bull markets |
| Disposition Effect |
Sell winners too early, hold losers |
Suppressed upside momentum |
Low volume on new highs |
| FOMO |
Fear of missing out |
Late-cycle volume spikes |
Volume surge + price acceleration |
| FUD |
Fear, uncertainty, doubt |
Overcorrection on bad news |
Price gap down on moderate news |
Prospect Theory Model
import numpy as np
def prospect_theory_value(outcome: float, reference: float,
lambda_loss: float = 2.25,
alpha: float = 0.88) -> float:
"""
Kahneman-Tversky prospect theory utility function.
lambda_loss: loss aversion coefficient (default 2.25 from original paper)
alpha: diminishing sensitivity (default 0.88)
Reference: Kahneman & Tversky (1979)
"""
delta = outcome - reference
if delta >= 0:
return delta ** alpha
else:
return -lambda_loss * ((-delta) ** alpha)
def aggregate_portfolio_pain(
positions: list[dict], # [{entry_price, current_price, size}]
) -> dict:
"""
Estimates aggregate psychological pain of a portfolio.
High pain = elevated sell pressure likely.
"""
total_pain = 0.0
losing_positions = 0
for pos in positions:
pnl = (pos['current_price'] - pos['entry_price']) * pos['size']
pv = prospect_theory_value(pos['current_price'], pos['entry_price'])
total_pain += pv
if pnl < 0:
losing_positions += 1
return {
'aggregate_utility': total_pain,
'losing_pct': losing_positions / len(positions) if positions else 0,
'sell_pressure_estimate': 'HIGH' if total_pain < -50 else 'MEDIUM' if total_pain < 0 else 'LOW',
}
Fear & Greed Cycle Detection
import pandas as pd
import numpy as np
def fear_greed_index(
price_data: pd.DataFrame, # OHLCV
volume_data: pd.Series,
funding_rate: pd.Series, # crypto perpetual funding
long_short_ratio: pd.Series,
lookback: int = 30,
) -> pd.Series:
"""
Composite Fear & Greed index [0-100].
0 = Extreme Fear, 100 = Extreme Greed.
"""
def normalise(series, window=lookback):
rolling_min = series.rolling(window).min()
rolling_max = series.rolling(window).max()
return (series - rolling_min) / (rolling_max - rolling_min + 1e-8) * 100
# Component signals
price_momentum = price_data['close'].pct_change(7) # 7-day return
volatility = price_data['close'].pct_change().rolling(30).std()
volume_trend = volume_data.rolling(7).mean() / volume_data.rolling(30).mean()
# Funding rate: positive = greed (longs paying shorts)
funding_norm = normalise(funding_rate)
# Long/short ratio: >1.5 = crowded longs (greed)
ls_norm = normalise(long_short_ratio)
# Composite (equal weights)
composite = (
normalise(price_momentum) * 0.25 +
(100 - normalise(volatility)) * 0.15 + # low vol = greed
normalise(volume_trend) * 0.20 +
funding_norm * 0.25 +
ls_norm * 0.15
)
return composite
def detect_fomo_signal(price: pd.Series, volume: pd.Series,
price_threshold: float = 0.05,
volume_threshold: float = 2.0) -> pd.Series:
"""
FOMO = price up >5% in 24h with volume >2× 30-day average.
Returns boolean series.
"""
price_surge = price.pct_change(24) > price_threshold
volume_spike = volume > (volume.rolling(720).mean() * volume_threshold)
return price_surge & volume_spike
Retail vs Institutional Behaviour
def classify_order_flow(trades_df: pd.DataFrame) -> pd.DataFrame:
"""
Classify trades as likely retail (small) or institutional (large).
Uses Kyle's lambda and trade size distribution.
"""
# Median trade size
median_size = trades_df['size'].median()
trades_df['type'] = 'retail'
trades_df.loc[trades_df['size'] > median_size * 10, 'type'] = 'institutional'
# Retail typically trades in round numbers
trades_df['is_round_number'] = (
(trades_df['price'] * 100).round(0) == (trades_df['price'] * 100)
)
return trades_df
def institutional_accumulation_signal(ohlcv: pd.DataFrame,
window: int = 20) -> pd.Series:
"""
Detect institutional accumulation: price flat/rising on above-average volume
without breakout. Classic Wyckoff accumulation signature.
"""
price_range = (ohlcv['high'] - ohlcv['low']) / ohlcv['close']
avg_volume = ohlcv['volume'].rolling(window).mean()
high_volume = ohlcv['volume'] > avg_volume * 1.5
tight_range = price_range < price_range.rolling(window).median()
return (high_volume & tight_range).astype(int)
Contrarian Indicators
def contrarian_signal(fear_greed: pd.Series,
extreme_greed_threshold: float = 80,
extreme_fear_threshold: float = 20) -> pd.Series:
"""
Contrarian signal: buy extreme fear, sell extreme greed.
Returns: -1 (sell), 0 (neutral), +1 (buy)
"""
signal = pd.Series(0, index=fear_greed.index)
signal[fear_greed <= extreme_fear_threshold] = 1 # buy the fear
signal[fear_greed >= extreme_greed_threshold] = -1 # sell the greed
return signal
def herd_intensity(long_short_ratio: pd.Series, window: int = 14) -> pd.Series:
"""
Measures deviation from 50/50 positioning.
High values = dangerous herding (setup for short squeeze or flush).
"""
deviation = (long_short_ratio - 1.0).abs()
return deviation / deviation.rolling(window).std()
Behavioural Patterns Quick Reference
| Pattern |
Setup |
Trade Direction |
| Capitulation |
Volume spike down + fear >90 |
Buy (bottom fishing) |
| Blow-off top |
Volume spike up + greed >90 |
Sell (distribution) |
| FOMO chase |
Price up 10%+ with retail surge |
Fade (short) |
| Smart money divergence |
Price rising but institutional selling |
Short |
| Panic selling |
Price falls >15% in 24h, LSR <0.5 |
Buy (oversold) |
| Wyckoff accumulation |
Flat price, high volume, low volatility |
Buy breakout |
Common Mistakes
- Trading against trends prematurely — contrarian signals need confirmation; wait for reversal candles
- Ignoring macro context — fear in a bull market is different from fear in a bear market
- Using single indicator — combine fear/greed + positioning + volume for robust signals
- Anthropomorphising — markets don't "feel" anything; biases are statistical tendencies, not certainties
- Overweighting retail sentiment — institutional flow dominates; retail sentiment is noise until extreme
Anti-Patterns
| Anti-Pattern |
Why It Fails |
Correct Approach |
| Modelling traders as purely rational agents |
Ignores loss aversion, anchoring, herding, and overconfidence that systematically drive market behaviour |
Incorporate behavioural finance models; calibrate against empirical data on bias magnitudes |
| Using put/call ratio in isolation as a sentiment indicator |
Hedging activity, market-making, and structural flows contaminate the signal; raw ratio is misleading |
Combine put/call with VIX term structure, fund flows, and positioning data for multi-factor sentiment |
| Treating all retail traders as a monolithic group |
Reddit retail, institutional retail (wealth management), and day traders have very different behaviours |
Segment retail by platform and behaviour: Reddit/social traders vs systematic retail vs passive retail |
| Going contrarian against every extreme sentiment reading |
Sometimes the crowd is right; strong trends persist through extreme sentiment for weeks or months |
Use contrarian signals only at structural levels (support/resistance); combine with technical confirmation |
| Ignoring institutional positioning data (COT reports) |
Retail sentiment without institutional context misses the dominant market force |
Overlay CFTC Commitment of Traders data; institutional positioning often leads retail sentiment shifts by days |
See also: this skill models the market CROWD — for logging and reviewing YOUR OWN executed trades and personal tilt/mistake tagging, use trade-journaling-and-review.