[{"data":1,"prerenderedAt":398},["ShallowReactive",2],{"blog-v-star":3},{"id":4,"title":5,"body":6,"date":385,"description":386,"extension":387,"featured":157,"meta":388,"navigation":157,"path":389,"seo":390,"stem":391,"tags":392,"__hash__":397},"blog\u002Fblog\u002Fv-star.md","v-star (v*) – High-Performance Actuarial Engine in Go",{"type":7,"value":8,"toc":372},"minimark",[9,18,23,38,49,52,56,59,87,91,94,99,102,106,109,113,116,233,237,240,266,269,273,276,296,299,303,306,332,336,339,357,368],[10,11,12,13,17],"p",{},"I've been building ",[14,15,16],"strong",{},"v-star"," — a high-performance actuarial engine written entirely in Go, with zero dependencies. It handles concurrent financial simulations, mass policy valuations, and Monte Carlo interest rate modeling at speeds that would make a spreadsheet user cry.",[19,20,22],"h2",{"id":21},"the-name","The Name",[10,24,25,26,28,29,33,34,37],{},"The name ",[14,27,16],{}," (v*) comes from actuarial notation. When an annuity's payments compound at rate ",[30,31,32],"em",{},"j"," but are discounted at rate ",[30,35,36],{},"i",", the adjusted discount factor is:",[39,40,45],"pre",{"className":41,"code":43,"language":44},[42],"language-text","v* = (1 + j) · v\n","text",[46,47,43],"code",{"__ignoreMap":48},"",[10,50,51],{},"It's an inside joke from university — one my lecturer and my coursemates (brothers and sister deployed to study Actuarial Science) will recognize. I figured if the math is niche enough, the project name should be too.",[19,53,55],{"id":54},"why-build-this","Why Build This?",[10,57,58],{},"Most actuarial software is either expensive enterprise tools or fragile Excel models that break when you look at them wrong. I wanted something that:",[60,61,62,69,75,81],"ul",{},[63,64,65,68],"li",{},[14,66,67],{},"Has zero dependencies"," — just the Go standard library, no third-party packages",[63,70,71,74],{},[14,72,73],{},"Is genuinely fast"," — leveraging goroutines for concurrent valuations",[63,76,77,80],{},[14,78,79],{},"Is auditable"," — pure, readable math implementations, no black boxes",[63,82,83,86],{},[14,84,85],{},"Teaches me Go deeply"," — building real financial systems beats reading tutorials",[19,88,90],{"id":89},"what-it-does","What It Does",[10,92,93],{},"v-star has a few core capabilities:",[95,96,98],"h3",{"id":97},"policy-valuation","Policy Valuation",[10,100,101],{},"Stream-processes CSV files of insurance policies and calculates present values using standard discount factors or the v-star adjusted factor. It handles 1M+ records in under 320ms with minimal memory usage — it streams data rather than loading everything into memory.",[95,103,105],{"id":104},"monte-carlo-simulation","Monte Carlo Simulation",[10,107,108],{},"Generates interest rate paths for stochastic modeling. 100,000 paths with 10 time steps? Done in about 100ms. The simulation uses geometric Brownian motion with configurable drift and volatility parameters.",[95,110,112],{"id":111},"cli-first-design","CLI-First Design",[10,114,115],{},"Everything runs from the command line. No web dashboard, no config files to edit — just flags and pipes:",[39,117,121],{"className":118,"code":119,"language":120,"meta":48,"style":48},"language-bash shiki shiki-themes vesper","# Calculate discount factors\n.\u002Fv-star -i 0.05 -j 0.02\n\n# Read CSV and benchmark valuation speed\n.\u002Fv-star read policies.csv --benchmark\n\n# Export results as JSON\n.\u002Fv-star read policies.csv --output=json\n\n# Monte Carlo with 100k paths\n.\u002Fv-star montecarlo --paths=100000 --steps=10 --drift=0.02 --volatility=0.15\n","bash",[46,122,123,132,152,159,165,179,184,190,202,207,213],{"__ignoreMap":48},[124,125,128],"span",{"class":126,"line":127},"line",1,[124,129,131],{"class":130},"ss8vJ","# Calculate discount factors\n",[124,133,135,139,143,146,149],{"class":126,"line":134},2,[124,136,138],{"class":137},"sNEDb",".\u002Fv-star",[124,140,142],{"class":141},"sZOz5"," -i",[124,144,145],{"class":137}," 0.05",[124,147,148],{"class":141}," -j",[124,150,151],{"class":137}," 0.02\n",[124,153,155],{"class":126,"line":154},3,[124,156,158],{"emptyLinePlaceholder":157},true,"\n",[124,160,162],{"class":126,"line":161},4,[124,163,164],{"class":130},"# Read CSV and benchmark valuation speed\n",[124,166,168,170,173,176],{"class":126,"line":167},5,[124,169,138],{"class":137},[124,171,172],{"class":141}," read",[124,174,175],{"class":141}," policies.csv",[124,177,178],{"class":141}," --benchmark\n",[124,180,182],{"class":126,"line":181},6,[124,183,158],{"emptyLinePlaceholder":157},[124,185,187],{"class":126,"line":186},7,[124,188,189],{"class":130},"# Export results as JSON\n",[124,191,193,195,197,199],{"class":126,"line":192},8,[124,194,138],{"class":137},[124,196,172],{"class":141},[124,198,175],{"class":141},[124,200,201],{"class":141}," --output=json\n",[124,203,205],{"class":126,"line":204},9,[124,206,158],{"emptyLinePlaceholder":157},[124,208,210],{"class":126,"line":209},10,[124,211,212],{"class":130},"# Monte Carlo with 100k paths\n",[124,214,216,218,221,224,227,230],{"class":126,"line":215},11,[124,217,138],{"class":137},[124,219,220],{"class":141}," montecarlo",[124,222,223],{"class":141}," --paths=100000",[124,225,226],{"class":141}," --steps=10",[124,228,229],{"class":141}," --drift=0.02",[124,231,232],{"class":141}," --volatility=0.15\n",[19,234,236],{"id":235},"performance","Performance",[10,238,239],{},"The numbers that matter:",[60,241,242,248,254,260],{},[63,243,244,247],{},[14,245,246],{},"CSV Parsing:"," ~4.8M rows\u002Fsec — zero-allocation streaming parser",[63,249,250,253],{},[14,251,252],{},"Valuation:"," ~3.1M rows\u002Fsec — 1M records in 320ms",[63,255,256,259],{},[14,257,258],{},"Monte Carlo:"," ~100k paths\u002Fsec",[63,261,262,265],{},[14,263,264],{},"Memory:"," Minimal — streams everything, nothing buffered",[10,267,268],{},"I wrote Python benchmarks with both Pandas and Polars for comparison. Go's concurrency model makes a massive difference here — goroutines handle each policy independently, and the runtime schedules them efficiently across cores.",[19,270,272],{"id":271},"architecture","Architecture",[10,274,275],{},"The codebase is organized cleanly:",[60,277,278,284,290],{},[63,279,280,283],{},[46,281,282],{},"cmd\u002F"," — CLI entry points",[63,285,286,289],{},[46,287,288],{},"pkg\u002F"," — Core packages (CSV parser, valuation engine, Monte Carlo simulator)",[63,291,292,295],{},[46,293,294],{},"docs\u002F"," — Documentation",[10,297,298],{},"The CSV parser is a custom streaming parser — no buffering entire files into memory. Records are parsed and processed as they arrive. The valuation engine calculates PVs concurrently using a worker pool pattern. Monte Carlo paths are simulated in parallel using goroutines with a seeded RNG.",[19,300,302],{"id":301},"lessons-learned","Lessons Learned",[10,304,305],{},"Building v-star taught me several things about Go and actuarial engineering:",[60,307,308,314,320,326],{},[63,309,310,313],{},[14,311,312],{},"Zero dependencies is a feature."," No supply chain risks, no version conflicts, no \"it works on my machine.\" If Go compiles, it runs.",[63,315,316,319],{},[14,317,318],{},"Goroutines make concurrency simple."," What would be painful threading in Python or Java is a one-liner in Go.",[63,321,322,325],{},[14,323,324],{},"Streaming beats buffering."," For large datasets, processing records one at a time is faster and uses less memory than loading everything first.",[63,327,328,331],{},[14,329,330],{},"CLI tools are underrated."," A tool that takes flags, reads stdin, and writes stdout can be composed into any workflow.",[19,333,335],{"id":334},"whats-next","What's Next",[10,337,338],{},"A few things I want to add:",[60,340,341,344,347,354],{},[63,342,343],{},"More actuarial functions — annuities, life tables, premium calculations",[63,345,346],{},"Parallel Monte Carlo with variance reduction techniques",[63,348,349,350,353],{},"A ",[46,351,352],{},"bench"," command that runs a full benchmark suite against the included test dataset",[63,355,356],{},"Statistical analysis of Monte Carlo results — percentiles, confidence intervals",[10,358,359,360,367],{},"The repo is at ",[361,362,366],"a",{"href":363,"rel":364},"https:\u002F\u002Fgithub.com\u002Flubasinkal\u002Fv-star",[365],"nofollow","github.com\u002Flubasinkal\u002Fv-star",". It's MIT licensed — feel free to use it, fork it, or break it.",[369,370,371],"style",{},"html pre.shiki code .ss8vJ, html code.shiki .ss8vJ{--shiki-default:#8B8B8B94}html pre.shiki code .sNEDb, html code.shiki .sNEDb{--shiki-default:#FFC799}html pre.shiki code .sZOz5, html code.shiki .sZOz5{--shiki-default:#99FFE4}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}",{"title":48,"searchDepth":134,"depth":134,"links":373},[374,375,376,381,382,383,384],{"id":21,"depth":134,"text":22},{"id":54,"depth":134,"text":55},{"id":89,"depth":134,"text":90,"children":377},[378,379,380],{"id":97,"depth":154,"text":98},{"id":104,"depth":154,"text":105},{"id":111,"depth":154,"text":112},{"id":235,"depth":134,"text":236},{"id":271,"depth":134,"text":272},{"id":301,"depth":134,"text":302},{"id":334,"depth":134,"text":335},"2026-03-20","A zero-dependency actuarial engine for concurrent financial simulations, built entirely in Go.","md",{},"\u002Fblog\u002Fv-star",{"title":5,"description":386},"blog\u002Fv-star",[393,394,395,396],"go","actuarial","simulation","finance","LLG9Vd21tpZr3RvfGqPDYS2jGz1mzeCFkKP8APD-Gcc",1779304901272]