An open API service indexing awesome lists of open source software.

https://github.com/lubasinkal/v-star

High-performance actuarial engine in Go — zero dependencies, CSV streaming at millions/sec, Monte Carlo, VaR/CTE, annuities, and mortality tables. Doesn't suck and Its NOT slow.
https://github.com/lubasinkal/v-star

actuarial actuarial-science annuity financial-modeling go golang high-performance monte-carlo mortality-rates quantitative-finance risk-management zero-dependencies

Last synced: 21 days ago
JSON representation

High-performance actuarial engine in Go — zero dependencies, CSV streaming at millions/sec, Monte Carlo, VaR/CTE, annuities, and mortality tables. Doesn't suck and Its NOT slow.

Awesome Lists containing this project

README

          

# v-star: The Actuarial Engine That Doesn't Suck

**Your calculations just got millions of times faster.**

Ever tried to run a valuation on a million-policy census? Watched Excel freeze, crash, or take hours? v-star is the answer. Built in Go — an actually fast language compared to R, Python, or VBA — it handles massive datasets and calculations in milliseconds while your coffee is still hot.

![CI](https://github.com/lubasinkal/v-star/actions/workflows/ci.yml/badge.svg)
![License](https://img.shields.io/badge/License-MIT-green)

---

## The Story

Actuarial science grad. Tired of:

- Excel crashing on big files
- VBA scripts that nobody understands
- Python code that felt slow (still better than VBA)
- Proprietary tools where you can't see the math
- Waiting to get accepted for a job

So I built v-star. Zero dependencies. All the actuarial stuff a gradute would think you'd need. Fast enough to make your laptop feel like a supercomputer.

Why the name? Comes from a joke in class: if premiums compound at rate **j** but you're discounting at **i**, the new discount factor is **v\*** = (1+j) × v. The star marks the difference.

---

## What Can It Do?

| Feature | What it means for you |
|---------|----------------------|
| **Present Value** | Standard & v\* discount factors — the core of everything |
| **Annuities** | Whole life, term, deferred — with real mortality tables |
| **Reserves** | Net premium, gross premium, prospective, retrospective |
| **Monte Carlo** | GBM + Vasicek mean-reverting models. 100k paths in ~27ms |
| **Risk Measures** | VaR, CTE, Expected Shortfall, confidence intervals |
| **Multiple Decrements** | Combine death, lapse, disability into a single table |
| **Big CSV Streaming** | Stream millions of rows without blowing up your RAM |
| **HTTP API** | Call from Python, R, Excel via REST endpoints |
| **Zero Dependencies** | Standard library only. No pip, no npm, no version hell |

**New:** Core interfaces added — `PathGenerator` (stochastic), `ContingencyCalculator` (annuities), `RecordWriter` (writer). Stream from any `io.Reader` via `StreamCensusFromReader`. `JSONRecord`/`CSVRecord` unified into `Record` type. `MortalityTable` now includes `Ex()` and `Lx()`. Parallel Monte Carlo (2.2× faster at 1M paths × 180 steps).

---

## Speed

Benchmarked on an Intel i5-8250U laptop (1.6-3.4 GHz, 8 cores, NVMe SSD). Plugged in.

| Benchmark | Time | Throughput |
|-----------|------|------------|
| **CSV Parsing** (10M rows, 288 MB) | 0.80s | **12.6M rows/s** |
| **Present Value** (single call, direct) | 2.6 ns | **380M / second** |
| **Present Value** (single call, constructor) | 22.8 ns | **44M / second** |
| **Annuity** (whole life, 90 terms) | 512 ns | **2M / second** |
| **Monte Carlo** (100k paths, 10 steps) | 27 ms | **3.7M paths/sec** |
| **Risk Report** (VaR + CTE, 100k losses) | 0.42 ms | **237M losses/sec** |
| **Valuation** (10M policies, parallel) | 37 ms | **272M policies/sec** |

### CSV Comparison (10M Rows)

| Tool | Time | Memory |
|------|------|--------|
| **v-star** (mmap) | **0.80s** | **1.3 GB (OS page cache)** |
| **v-star** (streaming) | 1.12s | ~0.2 MB |
| Polars | ~535ms | ~500 MB |
| Pandas | ~30s | >2 GB |

*v-star uses memory-mapped I/O for zero-copy parsing. The mmap path uses OS page cache (lazily paged, released to OS under memory pressure). The streaming path keeps memory constant regardless of file size.*

### Monte Carlo (100k Paths)

| Tool | Time | VaR/CTE |
|------|------|---------|
| **v-star** | **27ms** | ✓ (with confidence intervals) |
| Python/Numpy | ~2s | ✓ |
| R | ~5s | ✓ |

---

## Quick Start

```bash
# Install
go get github.com/lubasinkal/v-star

# Run examples
go run ./examples/quickstart # PV and duration
go run ./examples/monte_carlo_risk # Monte Carlo + VaR
go run ./examples/csv_valuation # Big CSV processing

# Build CLI
go build -o v-star ./cmd/v-star

# Process a policy CSV
./v-star read policies.csv --benchmark

# Run Monte Carlo
./v-star montecarlo --paths=100000 --steps=10

# Start HTTP server
./v-star serve
```

---

## Code Examples

### For Actuarial Students & Professionals

You already know the math:

```go
package main

import (
"fmt"
"log"

"github.com/lubasinkal/v-star/pkg/annuities"
"github.com/lubasinkal/v-star/pkg/mortality"
"github.com/lubasinkal/v-star/pkg/rates"
"github.com/lubasinkal/v-star/pkg/reserves"
)

func main() {
// Present value — like Excel's =PV(0.05, 20, 0, -100000)
converter := rates.NewRateConverter(0.05)
pv := converter.PresentValue(100000, 20)
fmt.Printf("PV: %.2f\n", pv) // 37,688.95

// Build a mortality table inline
qx := []float64{0, 0.01, 0.02, 0.03, 0.04, 0.05, 0.06, 0.07, 0.08, 0.09, 0.10}
mort := mortality.NewTable("example", qx)

// Annuity with mortality table
ann := annuities.NewAnnuityCalculator(converter, mort)
pv = ann.WholeLifeImmediate(65, 1000)
fmt.Printf("Annuity PV: %.2f\n", pv)

// Reserve calculation
policy := reserves.PolicySpec{Age: 50, Term: 20, SumAssured: 100000}
reserve := reserves.NetPremiumReserve(policy, converter, mort)
fmt.Printf("Reserve: %.2f\n", reserve)

// Multiple decrements (death + lapse combined)
death := mortality.NewTable("death", []float64{0, 0.01, 0.02})
lapse := mortality.NewTable("lapse", []float64{0, 0.05, 0.10})
dt := mortality.NewDecrementTable([]*mortality.Table{death, lapse}, nil)
fmt.Printf("Total qx at age 1: %.4f\n", dt.Qx(1))
fmt.Printf("Death cause qx at age 1: %.4f\n", dt.QxByCause(1, 0))
}
```

### For Developers

```go
package main

import (
"context"
"fmt"
"log"
"os"

"github.com/lubasinkal/v-star/pkg/concurrency"
"github.com/lubasinkal/v-star/pkg/rates"
"github.com/lubasinkal/v-star/pkg/reader"
"github.com/lubasinkal/v-star/pkg/risk"
"github.com/lubasinkal/v-star/pkg/stochastic"
"github.com/lubasinkal/v-star/pkg/writer"
)

func main() {
converter := rates.NewRateConverter(0.05)

// Vasicek mean-reverting rates (instead of GBM)
vg := stochastic.NewVasicekGenerator(0.05, 0.04, 0.5, 0.02)
path := vg.GeneratePath(10, 1.0)
fmt.Println("Vasicek final rate:", path[10])

// Monte Carlo + VaR with confidence intervals
rg := stochastic.NewRateGeneratorWithSeed(0.05, 0.02, 0.15, 42)
paths := rg.GeneratePaths(100000, 10, 1.0)
losses := make([]float64, len(paths))
for i, p := range paths {
losses[i] = 1000000 * (0.05/p[10] - 1)
if losses[i] < 0 {
losses[i] = 0
}
}
report := risk.ComputeReport(losses)
fmt.Printf("VaR 95%%: %.2f, CTE 95%%: %.2f\n", report.VaR95, report.CTE95)

// Stream census records (simulating from memory)
records := []reader.CensusRecord{
{Age: 30, Sex: "M", PolicyType: "term", SumAssured: 100000, Term: 20},
{Age: 45, Sex: "F", PolicyType: "whole", SumAssured: 200000, Term: 15},
}

// Generic parallel worker pool with context cancellation
wp := concurrency.NewWorkerPool(4, func(r reader.CensusRecord) float64 {
return converter.PresentValue(r.SumAssured, r.Term)
})
totalPV := wp.ProcessBatch(records)
fmt.Printf("Total PV: %.2f\n", totalPV)

ctx := context.Background()
result, err := wp.ProcessBatchContext(ctx, records)
if err != nil {
log.Fatal(err)
}
fmt.Printf("Context result: %.2f\n", result)

// Monte Carlo parallel — concrete types satisfy PathGenerator implicitly
gen := stochastic.NewRateGenerator(0.05, 0.02, 0.15)
paths = gen.GeneratePathsParallel(100000, 10, 0, 1.0)
fmt.Printf("Generated %d paths in parallel\n", len(paths))

// Pick output format at runtime
w := writer.NewRecordWriter(os.Stdout, "json")
defer w.Close()
w.WriteRecord(writer.Record{
Age: 30, Sex: "M", SumAssured: 100000, Term: 20, PresentValue: 37688.95,
})
}
```

### CSV Reader — Which one to use

| Function | When to use |
|----------|-------------|
| `StreamCensus` | Process an actuarial census CSV row by row (auto-detects columns); accumulate your own metrics |
| `StreamCensusChunked` | Batch processing (database inserts, API calls) |
| `StreamCensusFromReader` | Stream census data from any `io.Reader` (stdin, HTTP body, in-memory) |
| `StreamCSV` | Generic CSV with string fields (non-standard column layout) |
| `StreamCSVRaw` | Generic CSV with zero-allocation byte slices |
| `GetHeaders` | Inspect column headers before deciding parsing strategy |

**All standard library. Zero external dependencies.**

---

## CLI

```bash
# Calculate discount factors
./v-star -i 0.05 -j 0.02

# Process a policy CSV
./v-star read policies.csv --benchmark
./v-star read policies.csv --table=mortality.csv --output=json
./v-star read policies.csv --output=csv --limit=10000
./v-star read policies.csv --output=report
./v-star read policies.csv --interest=0.04 --header=true

# Run Monte Carlo
./v-star montecarlo --paths=100000 --steps=10 --seed=42
./v-star montecarlo --paths=1000000 --steps=180 --workers=8 --drift=0.03 --volatility=0.20

# Run benchmark suite
./v-star bench

# Start HTTP server
./v-star serve --port=8080
```

---

## HTTP API

Start the server:

```bash
./v-star serve --port=8080
```

All endpoints return JSON. The server includes CORS (cross-origin), request logging, and graceful shutdown on Ctrl+C.

| Endpoint | Method | Description |
|----------|--------|-------------|
| `/health` | GET | Health check |
| `/value` | POST | Present value calculation (batch records) |
| `/simulate` | POST | Stochastic simulation — GBM or Vasicek + risk metrics (VaR, CTE) |
| `/annuity` | POST | Life-contingent annuity and net single premium calculations |
| `/reserve` | POST | Policy reserve calculations (net/gross/premium/prospective/retrospective) |

### Examples

```bash
# Present value
curl -s -X POST http://localhost:8080/value \
-H "Content-Type: application/json" \
-d '{"interest_rate":0.05,"records":[{"sum_assured":100000,"term":20}]}'

# Monte Carlo
curl -s -X POST http://localhost:8080/simulate \
-H "Content-Type: application/json" \
-d '{"num_paths":10000,"steps":10,"initial_rate":0.05,"drift":0.02,"volatility":0.15}'

# Annuity
curl -s -X POST http://localhost:8080/annuity \
-H "Content-Type: application/json" \
-d '{"interest_rate":0.05,"qxs":[0.001],"age":30,"amount":1000,"computation":"whole_life_immediate"}'

# Reserve
curl -s -X POST http://localhost:8080/reserve \
-H "Content-Type: application/json" \
-d '{"interest_rate":0.05,"qxs":[0.001],"age":30,"term":20,"sum_assured":100000,"method":"net_premium"}'
```

### Python

```python
import requests

# Present value
resp = requests.post("http://localhost:8080/value", json={
"interest_rate": 0.05,
"records": [{"sum_assured": 100000, "term": 20}]
})
print(resp.json())

# Monte Carlo
resp = requests.post("http://localhost:8080/simulate", json={
"num_paths": 100000, "steps": 10,
"initial_rate": 0.05, "drift": 0.02, "volatility": 0.15
})
print(resp.json()) # {"var_95": ..., "cte_95": ...}

```

**v-star's HTTP API works from any language** — Python, R, JavaScript,
TypeScript, Excel VBA, Julia, Rust, etc. See
[examples/api-clients/](./examples/api-clients/) for full examples in
Python, R, JavaScript, TypeScript, and cURL.

---

## Why Go?

- **Speed** — Compiles to native code, no interpreter overhead. 380M PV calculations/sec
- **Zero deps** — Standard library only. No pip, no npm, no version conflicts
- **Readable** — Every formula is right there in the source. Audit-friendly
- **Concurrent** — Goroutines make parallelism trivial
- **Portable** — One binary, runs anywhere

---

## Who's It For?

| Person | Why v-star |
|--------|------------|
| **Actuarial student** | Learn by reading the code. Fast calculations for assignments. |
| **Actuary** | Replace slow Excel/VBA. Process big censuses in seconds. |
| **Analyst** | Stream big CSVs without crashing. Get results, not errors. |
| **Developer** | Build insurance/risk tools via HTTP API from any language. |
| **Risk manager** | Run Monte Carlo + VaR in production. Fast. |

---

## What's Coming Next?

- **v0.8.0** — Reserve methods, CensusSource interface, profit testing, Dockerfile, API freeze
- **v1.0.0** — Stable API, 90%+ test coverage,

Full roadmap: [ROADMAP.md](./ROADMAP.md)

---

## Contributing

PRs welcome. See [CONTRIBUTING.md](./CONTRIBUTING.md). Found a bug? [Open an issue](https://github.com/lubasinkal/v-star/issues).

---

## License

MIT — do whatever you want with it. See [LICENSE](./LICENSE).