Skip to content

jcharit1/Ostrich-Project-v2

Repository files navigation

Ostrich Project

A computational economics research tool that models the "ostrich effect" in investing -- the tendency for investors to avoid checking their portfolio when markets are doing poorly.

What It Does

Given an investor's historical pattern of "looking" or "not looking" at their portfolio, this program uses dynamic programming to find the optimal decision rule for a hypothetical rational investor with loss-averse preferences. It then compares the model's predicted look/don't-look decisions against the actual observed behavior, producing an R-squared goodness-of-fit measure.

The model treats the investor as an infinite-horizon utility maximizer whose period utility is defined over portfolio gains and losses relative to a benchmark (rather than consumption). The state space is four-dimensional:

  • Approximate wealth (what the investor believes their portfolio is worth)
  • Benchmark (reference point for gains/losses)
  • Time since last looked
  • Wealth at time of last look

Utility Parameters

Parameter Symbol Description
alpha A Utility multiplier when looking (attention premium)
beta B Discount factor (0 = closed-form solver available)
gamma G Loss aversion multiplier
delta D Benchmark update weight when looking
theta T Benchmark update weight when not looking

Solver Variants

  • General (Riemann sum): Full backward induction with numerical integration (beta != 0)
  • Beta-zero (closed form): Analytical expected utility when beta = 0
  • Standard utility: Linear loss aversion
  • Exponential utility: Exponential loss aversion

Installation

Prerequisites

  • C++17 compiler (g++ or clang++)
  • CMake >= 3.15
  • Python >= 3.9 (for the Python interface)
  • pybind11 (for the Python extension)

Build the C++ library and CLI

git clone https://github.com/jcharit1/Ostrich-Project.git
cd Ostrich-Project
mkdir build && cd build
cmake ..
make

This produces:

  • build/ostrich -- standalone CLI executable
  • build/libostrich_lib.a -- static C++ library
  • build/ostrich_core.*.so -- Python extension module (if pybind11 is found)

Install the Python package

pip install pybind11
pip install -e ".[all]"   # Installs with SQLAlchemy + pandas support

Or install with only the dependencies you need:

pip install -e .            # Core only (no database/pandas)
pip install -e ".[db]"      # With SQLAlchemy for database storage
pip install -e ".[analysis]" # With pandas for DataFrames

Build legacy executables (original code)

cd build
cmake .. -DBUILD_LEGACY=ON
make

Input Data Format

Investor data files are whitespace-delimited with 4 columns and no header:

ID    LOOK   PERSONAL_RETURN   MARKET_RETURN
74    1      1.415634          1.283377
74    1      0.856195          0.575592
74    0      0.654541          2.312507
74    0      0.540954          0.254741
  • ID: Investor identifier
  • LOOK: 1 = looked, 0 = didn't look, -999 = missing
  • PERSONAL_RETURN: Gross personal portfolio return (not log)
  • MARKET_RETURN: Gross market return (not log)

Test data is included at test/test_investor_data/88808sample74.txt (504 rows).


Recipes

1. Single run from the command line (C++ CLI)

./build/ostrich \
    test/test_investor_data/88808sample74.txt \
    88808 504 Test \
    0.0 0.8 3.0 1.0 0.5 \
    10.0 10 1 RS 1000 0.001 0.001 Test

Arguments in order: data_file, sample, nrows, param_name, alpha, beta, gamma, delta, theta, t_max, npart, z, EU_type, mc_num, alpha_ci, error_pct, sys.

Output (to stdout):

74, 0, 0.8, 3, 1, 0.5, 10, 0.529762

2. Single run from Python

from ostrich import OstrichModel

model = OstrichModel(
    data_file="test/test_investor_data/88808sample74.txt",
    max_row=504,
    num_partitions=10,
    t_max=10,
)

result = model.run(alpha=0.0, beta=0.8, gamma=3.0, delta=1.0, theta=0.5)
print(result)
# {'investor_id': 74.0, 'alpha': 0.0, 'beta': 0.8, 'gamma': 3.0,
#  'delta': 1.0, 'theta': 0.5, 'num_partitions': 10, 'fit': 0.5297...}

3. Parameter grid search

from ostrich import OstrichModel

model = OstrichModel(
    data_file="test/test_investor_data/88808sample74.txt",
    max_row=504,
    num_partitions=10,
)

results = model.run_grid(
    alpha_values=[0.0, 1.0, 2.0],
    beta_values=[0.0, 0.4, 0.8],
    gamma_values=[2.0, 3.0, 5.0],
    delta_values=[0.5, 1.0],
    theta_values=[0.3, 0.5, 0.7],
)

# Results are sorted by fit (best first)
print(f"Tested {len(results)} combinations")
print(f"Best fit: {results[0]['fit']:.4f}")
print(f"Best params: alpha={results[0]['alpha']}, beta={results[0]['beta']}, "
      f"gamma={results[0]['gamma']}, delta={results[0]['delta']}, "
      f"theta={results[0]['theta']}")

4. Save results to CSV files

from ostrich import OstrichModel, ResultStore

store = ResultStore(mode="file", output_dir="results/", file_format="csv")

model = OstrichModel("test/test_investor_data/88808sample74.txt", max_row=504)
results = model.run_grid(
    alpha_values=[0.0, 2.0],
    beta_values=[0.0, 0.8],
    gamma_values=[3.0],
    delta_values=[1.0],
    theta_values=[0.5],
    store=store,
)

# Results are written to results/results_inv74.csv as they complete
print(f"Saved {len(store)} results to results/")

5. Save results to JSON

from ostrich import ResultStore

store = ResultStore(mode="file", output_dir="results/", file_format="json")

# ... run model with store=store ...
# Each result is appended as a JSON line to results/result_inv74_n10.json

6. Save results to a SQLite database

from ostrich import OstrichModel, ResultStore

store = ResultStore(mode="database", db_url="sqlite:///ostrich_results.db")

model = OstrichModel("test/test_investor_data/88808sample74.txt", max_row=504)
result = model.run(alpha=0.0, beta=0.8, gamma=3.0, delta=1.0, theta=0.5, store=store)

# Results are inserted into the 'ostrich_results' table
print(store.get_best(n=3))  # Top 3 by fit

7. Save results to PostgreSQL

from ostrich import ResultStore

store = ResultStore(
    mode="database",
    db_url="postgresql://user:password@host:5432/mydb",
    table_name="ostrich_fits",
)

# ... run model with store=store ...
# Results go to the 'ostrich_fits' table in PostgreSQL

8. Convert results to a pandas DataFrame

from ostrich import OstrichModel, ResultStore

store = ResultStore(mode="memory")
model = OstrichModel("test/test_investor_data/88808sample74.txt", max_row=504)

results = model.run_grid(
    alpha_values=[0.0, 1.0, 2.0],
    beta_values=[0.0, 0.8],
    gamma_values=[3.0],
    delta_values=[1.0],
    theta_values=[0.5],
    store=store,
)

df = store.to_dataframe()
print(df.sort_values("fit", ascending=False))
#    investor_id  alpha  beta  gamma  delta  theta  num_partitions       fit
# 3         74.0    0.0   0.8    3.0    1.0    0.5              10  0.529762
# 0         74.0    0.0   0.0    3.0    1.0    0.5              10  0.486111
# ...

9. Use the exponential utility variant

model = OstrichModel(
    data_file="test/test_investor_data/88808sample74.txt",
    max_row=504,
    utility_type="exponential",  # Instead of "standard"
)

result = model.run(alpha=0.0, beta=0.8, gamma=3.0, delta=1.0, theta=0.5)

10. Use the Python CLI

# Single run
python -m ostrich run test/test_investor_data/88808sample74.txt 504 \
    --alpha 0.0 --beta 0.8 --gamma 3.0 --delta 1.0 --theta 0.5

# Grid search with CSV output
python -m ostrich grid test/test_investor_data/88808sample74.txt 504 \
    --params params.json --output results/

# Grid search with database output
python -m ostrich grid test/test_investor_data/88808sample74.txt 504 \
    --params params.json --db-url sqlite:///results.db

Where params.json looks like:

{
    "alpha": [0.0, 1.0, 2.0],
    "beta": [0.0, 0.4, 0.8],
    "gamma": [2.0, 3.0],
    "delta": [0.5, 1.0],
    "theta": [0.3, 0.5]
}

11. Use the C++ library directly

#include "ostrich/ostrich.h"
#include <iostream>

int main() {
    ostrich::ModelParams params;
    params.alpha = 0.0;
    params.beta  = 0.8;
    params.gamma = 3.0;
    params.delta = 1.0;
    params.theta = 0.5;

    ostrich::NumericalConfig config;
    config.num_partitions = 10;
    config.t_max          = 10;
    config.mc_num         = 1000;
    config.alpha_ci       = 0.001;
    config.error_pct      = 0.001;
    config.method         = ostrich::IntegrationMethod::RIEMANN_SUM;
    config.utility_type   = ostrich::UtilityType::STANDARD;
    config.solver_type    = ostrich::SolverType::GENERAL;

    auto result = ostrich::run_model("data.txt", 504, params, config);
    std::cout << "Fit: " << result.fit << std::endl;

    return 0;
}

Compile with:

g++ -std=c++17 -O3 -I include my_program.cpp -L build -lostrich_lib -o my_program

Project Structure

Ostrich-Project/
  include/ostrich/       # Refactored C++ headers (struct-based API)
  src/ostrich/           # Refactored C++ implementation
  python/ostrich/        # Python frontend package
  include/               # Legacy C headers (preserved)
  src/                   # Legacy C++ source (preserved)
  test/                  # Test data and parameters
  data/                  # Parameter tables
  CMakeLists.txt         # Build system (new code)
  makefile               # Build system (legacy code)
  pyproject.toml         # Python package config
  setup.py               # Python build with C++ extension

Contributing

  1. Fork it
  2. Create your feature branch: git checkout -b my-new-feature
  3. Commit your changes: git commit -am 'Add some feature'
  4. Push to the branch: git push origin my-new-feature
  5. Submit a pull request

License

MIT License

Copyright (c) 2016 Jimmy Charite

Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

About

Modular refactor of the Ostrich Project (investor information acquisition model) with Python frontend — updated from github.com/jcharit1/Ostrich-Project

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors