Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
90 changes: 90 additions & 0 deletions examples/information_cascades/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
# Information Cascades & Trading Behavior Model

This example implements a hybrid model of **Information Cascades and Trading Behavior** using **Mesa** and **Solara**.

The model studies how individual opinions evolve through repeated pairwise interactions (based on bounded confidence) and how investor overconfidence leads to excessive trading and wealth destruction.

---

## Model Description

Each investor agent holds a continuous opinion value in the range **(-1, 1)**, an overconfidence level between **(1.0, 5.0)**, and an initial wealth of **1000.0**.
At each time step:

1. A random pair of agents is selected.
2. If the difference between their opinions is less than a confidence threshold **ε (epsilon)**, they interact.
3. During an interaction, both agents adjust their opinions toward each other by a fraction **μ (mu)**, adjusted by their stubbornness (overconfidence).
4. Agents then execute trades with a probability based on their confidence level. Each trade deducts a fixed transaction cost from their wealth.

Depending on parameter values, the model can exhibit:
- Herd formation (Consensus)
- Echo chambers (Polarization and Fragmentation)
- Systematic wealth depletion due to overtrading

---

## Parameters

| Parameter | Description |
|-------------------------|------------|
| `n` | Number of investors in the market |
| `epsilon (ε)` | Confidence threshold controlling whether agents interact |
| `mu (μ)` | Convergence rate controlling how strongly opinions are updated |
| `transaction_cost` | The fee deducted from an agent's wealth per executed trade |

---

## Collected Metrics

The model tracks the following quantities over time:

- **Variance** – dispersion of opinions in the population
- **Avg Wealth** – the average remaining capital across all agents

These metrics are visualized alongside individual opinion trajectories and wealth distribution.

---

## Visualization

This example includes a Solara-based interactive visualization that shows:

- Opinion trajectories of all agents (Herd Formation)
- A scatter plot validating that "Trading is Hazardous to Your Wealth" (Confidence vs. Wealth)
- Opinion Variance over time
- Average Wealth over time
- Real-time trading performance stats

The market parameters can be adjusted in real-time using the sliders.

---

## Installation

To install the dependencies, use `pip` to install the requirements:

```bash
$ pip install -r requirements.txt
```

From this directory, run:
```bash
$ solara run app.py
```

Then open your browser to local host http://localhost:8765/ and press Reset, then Step or Play.

## Files
- model.py: Defines the TradingDWModel, including market parameters, agent interactions, and data collection.
- agents.py: Defines the InvestorAgent class, the logic for updating agent opinions, and the trading execution mechanism.
- app.py: Contains the code for the interactive Solara visualization, including opinion trajectories, scatter plots, and custom performance metrics.


## References
- Barber, B. M., & Odean, T. (2000).
Trading is hazardous to your wealth: The common stock investment performance of individual investors.
The Journal of Finance, 55(2), 773-806.

- Banerjee, A. V. (1992).
A simple model of herd behavior.
The Quarterly Journal of Economics, 107(3), 797-817.
134 changes: 134 additions & 0 deletions examples/information_cascades/app.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,134 @@
import matplotlib.pyplot as plt
import solara
import solara.lab
from information_cascades.model import TradingDWModel
from mesa.visualization import SolaraViz, make_plot_component


def TradingPerformanceStats(model):
agents = list(model.agents)
if not agents:
return solara.Text("initializing...")

avg_gross = sum(a.gross_wealth for a in agents) / len(agents)
avg_net = sum(a.net_wealth for a in agents) / len(agents)

sorted_agents = sorted(agents, key=lambda x: x.trades)
n = len(sorted_agents)
split = max(1, n // 5)

low_traders = sorted_agents[:split]
high_traders = sorted_agents[-split:]

avg_net_low = sum(a.net_wealth for a in low_traders) / len(low_traders)
avg_net_high = sum(a.net_wealth for a in high_traders) / len(high_traders)

return solara.Card(
title="Barber & Odean (2000) Validation",
children=[
solara.Markdown(f"**Market Avg (Gross)**: {avg_gross:.2f}"),
solara.Markdown(f"**Market Avg (Net)**: {avg_net:.2f}"),
solara.Markdown("---"),
solara.Markdown(
f"**Low Turnover Top 20% Net Wealth**: <span style='color:green; font-weight:bold;'>{avg_net_low:.2f}</span>"
),
solara.Markdown(
f"**High Turnover Top 20% Net Wealth**: <span style='color:red; font-weight:bold;'>{avg_net_high:.2f}</span>"
),
],
)


def WealthVsConfidenceScatter(model):
fig, ax = plt.subplots(figsize=(6, 4), constrained_layout=True)
agents = list(model.agents)
confidences = [a.confidence for a in agents]
wealths = [a.net_wealth for a in agents]

if wealths:
avg_w = sum(wealths) / len(wealths)
ax.scatter(confidences, wealths, alpha=0.6, c=wealths, cmap="RdYlGn")

w_min, w_max = min(wealths), max(wealths)
padding = (w_max - w_min) * 0.2 if w_max > w_min else 10
ax.set_ylim(w_min - padding, w_max + padding)

ax.axhline(avg_w, color="blue", linestyle="--", alpha=0.5)
ax.fill_between(
[1, 5],
w_min - padding,
avg_w,
color="red",
alpha=0.1,
label="Underperforming",
)

ax.set_xlabel("Confidence Level")
ax.set_ylabel("Net Wealth")
ax.set_title("Trading is Hazardous to Your Wealth")

return solara.FigureMatplotlib(fig)


def OpinionTrajectoryPlot(model):
fig, ax = plt.subplots(figsize=(6, 4), constrained_layout=True)
df = model.datacollector.get_agent_vars_dataframe()
if df.empty:
return solara.FigureMatplotlib(fig)

opinions = df["opinion"].unstack()
opinions.plot(ax=ax, legend=False, alpha=0.4)
ax.set_xlabel("Steps")
ax.set_ylabel("Opinion")
ax.set_title("Herd Formation (Banerjee, 1992)")
return solara.FigureMatplotlib(fig)


model_params = {
"n": {
"type": "SliderInt",
"value": 100,
"label": "Number of Investors",
"min": 20,
"max": 300,
"step": 1,
},
"epsilon": {
"type": "SliderFloat",
"value": 0.6,
"label": "Confidence Threshold (ε)",
"min": 0.01,
"max": 1.0,
"step": 0.01,
},
"mu": {
"type": "SliderFloat",
"value": 0.1,
"label": "Convergence Rate (μ)",
"min": 0.01,
"max": 0.5,
"step": 0.01,
},
"transaction_cost": {
"type": "SliderFloat",
"value": 0.5,
"label": "Transaction Cost",
"min": 0,
"max": 10,
"step": 0.5,
},
}

initial_model = TradingDWModel()

page = SolaraViz(
model=initial_model,
model_params=model_params,
components=[
TradingPerformanceStats,
OpinionTrajectoryPlot,
WealthVsConfidenceScatter,
make_plot_component("Variance"),
make_plot_component(["Avg Gross Wealth", "Avg Net Wealth"]),
],
)
Empty file.
36 changes: 36 additions & 0 deletions examples/information_cascades/information_cascades/agents.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
from mesa import Agent


class InvestorAgent(Agent):
"""
While Banerjee's Herding Effect highlights how investors blindly follow the crowd, Barber & Odean's Overconfidence
Theory explains their stubborn reliance on flawed personal judgment; together, they amplify irrational market
volatility and pricing inefficiencies.
"""

def __init__(self, model, opinion, confidence):
super().__init__(model)
self.opinion = opinion
self.confidence = confidence
"""
The core of the Barber and Odean theory lies in the critical distinction between gross returns
and net returns, demonstrating how overconfident investors' excessive trading costs erode potential gains.
"""
self.gross_wealth = (
1000.0 # Theoretical Market Wealth Under a Buy-and-Hold Strategy (Gross)
)
self.net_wealth = 1000.0 # Actual Wealth After Deducting Frequent Trading Commissions and Fees (Net)
self.trades = 0

def update_opinion(self, other_opinion, mu):
# The more inflated the confidence, the more stubborn the bias (resulting in a smaller effective mu
effective_mu = mu / self.confidence
self.opinion += effective_mu * (other_opinion - self.opinion)

def execute_trade(self):
# Barber & Odean: Overconfidence leads to excessive turnover rates.
trade_prob = 0.05 * self.confidence
if self.random.random() < trade_prob:
self.trades += 1
# Only net wealth accounts for the deduction of transaction friction costs.
self.net_wealth -= self.model.transaction_cost
74 changes: 74 additions & 0 deletions examples/information_cascades/information_cascades/model.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
import statistics

from mesa import Model
from mesa.datacollection import DataCollector

from .agents import InvestorAgent


class TradingDWModel(Model):
def __init__(self, n=100, epsilon=0.2, mu=0.5, transaction_cost=2.0, rng=None):
super().__init__(rng=rng)
self.n = n
self.epsilon = epsilon
self.mu = mu
self.transaction_cost = transaction_cost
self.attempted_interactions = 0
self.accepted_interactions = 0

self.datacollector = DataCollector(
model_reporters={
"Variance": self.compute_variance,
"Avg Gross Wealth": lambda m: (
statistics.mean([a.gross_wealth for a in m.agents])
if m.agents
else 0
),
"Avg Net Wealth": lambda m: (
statistics.mean([a.net_wealth for a in m.agents]) if m.agents else 0
),
},
agent_reporters={
"opinion": "opinion",
"net_wealth": "net_wealth",
"confidence": "confidence",
"trades": "trades",
},
)

for _ in range(self.n):
op = self.random.uniform(-1, 1)
conf = self.random.uniform(1.0, 5.0)
agent = InvestorAgent(self, op, conf)
self.agents.add(agent)

self.datacollector.collect(self)

def step(self):
agent_list = list(self.agents)

# Simulating Natural Market Volatility Returns (Random Walk with Positive Drift
market_return = self.random.normalvariate(0.001, 0.01)
for agent in agent_list:
agent.gross_wealth *= 1 + market_return
agent.net_wealth *= 1 + market_return

for _ in range(self.n):
agent_a, agent_b = self.random.sample(agent_list, 2)
self.attempted_interactions += 1

# Banerjee: Communication within cognitive thresholds leads to opinion convergence (herd formation).
if abs(agent_a.opinion - agent_b.opinion) < self.epsilon:
old_op_a = agent_a.opinion
agent_a.update_opinion(agent_b.opinion, self.mu)
agent_b.update_opinion(old_op_a, self.mu)

agent_a.execute_trade()
agent_b.execute_trade()
self.accepted_interactions += 1

self.datacollector.collect(self)

def compute_variance(self):
opinions = [a.opinion for a in self.agents]
return statistics.variance(opinions) if len(opinions) > 1 else 0
7 changes: 7 additions & 0 deletions examples/information_cascades/requirements.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
mesa>3.0.0
solara
matplotlib
pandas
numpy
networkx
altair