Skip to main content

A compiler for the ValuaScript financial modeling language.

Project description

ValuaScript: A Declarative Language for High-Performance Monte Carlo Simulations

Build Status License: MIT C++ Version Python Version

ValuaScript combines a simple, declarative scripting language with a high-performance, multi-threaded C++ engine to create, run, and analyze complex quantitative models. It is purpose-built to deliver both clarity and speed, making sophisticated Monte Carlo simulations accessible and maintainable.


The Core Philosophy: A Vocabulary for Simulations

Financial and scientific modeling often forces a choice between two extremes: the intuitive but slow, error-prone nature of spreadsheets, and the powerful but verbose, complex nature of general-purpose programming languages.

ValuaScript was created to bridge this gap. It is not a general-purpose language; it is a vocabulary for describing simulations.

This approach is guided by three principles:

  • Declarative & Readable: A ValuaScript file describes what the model is, not how to compute it. There are no loops, complex data structures, or pointers. The script reads like a novel or a specification sheet, making models easy to write, review, and maintain for domain experts and programmers alike.

  • High-Performance by Design: All the heavy lifting—numerical calculations, statistical sampling, and data aggregation—is handled by a multi-threaded C++17 engine. Scripting a model in ValuaScript is simply wiring together these pre-compiled, high-speed components. A simulation that might take minutes in other tools is executed in seconds.

  • Extensible through Contribution: The language grows by expanding its C++ core. Need a niche financial model or a specific epidemiological simulation? Implement it once in optimized C++, and it instantly becomes a new "word" in the ValuaScript vocabulary for everyone to use. This creates a powerful, community-driven flywheel for growth.

See it in Action: A Simple Modular DCF Model

This example demonstrates how ValuaScript combines modules, user-defined functions, and stochastic variables to create a model that is clean, readable, and powerful.

File: modules/finance_utils.vs

@module

func calculate_wacc(beta: scalar) -> scalar {
    """Calculates a simple Weighted Average Cost of Capital."""
    let equity_premium = 0.05
    let risk_free_rate = 0.02
    return risk_free_rate + beta * equity_premium
}

func present_value(cashflows: vector, discount_rate: scalar) -> scalar {
    """Discounts a vector of cash flows to their present value."""
    return npv(discount_rate, cashflows)
}

File: main.vs

@import "modules/finance_utils.vs"

@iterations = 50_000
@output = dcf_value
@output_file = "dcf_simulation.csv"

# --- Inputs ---
let initial_revenue = 1000.0
let asset_beta = 1.2

# Stochastic variable for revenue growth
let revenue_growth = Normal(0.10, 0.15)

# --- Logic ---
let discount_rate = calculate_wacc(asset_beta)
let future_revenues = grow_series(initial_revenue, revenue_growth, 5)

# Call the imported function to get the final result
let dcf_value = present_value(future_revenues, discount_rate)

Run the Simulation

One command compiles, runs 50,000 trials with optimizations, and plots the results.

vsc main.vs --run -O --plot

Architecture: AOT Compiler & Virtual Machine

The project follows a modern Ahead-of-Time (AOT) Compiler and Virtual Machine (VM) model. This clean separation of concerns ensures maximum performance and compile-time safety by eliminating runtime analysis and interpretation.

  1. The vsc Compiler (Python): This is the "brain." It parses the high-level .vs script, resolves the @import graph, performs deep semantic validation, runs optimizations, and emits a low-level JSON bytecode.
  2. The vse Engine (C++): This is the "muscle." It is a fast, multi-threaded executable that does no analysis; it simply loads the pre-compiled bytecode and executes the instructions at maximum speed.
graph TD
    subgraph "Development Phase"
        A["<br/><b>ValuaScript Project</b><br/><i>(.vs files)</i>"]
    end
    subgraph "Compilation Phase"
        B["<br/><b>vsc Compiler (Python)</b><br/>Parses, validates, optimizes,<br/>and generates bytecode"]
    end
    subgraph "Execution Phase"
        C["<br/><b>JSON Bytecode</b><br/><i>Low-level execution plan</i>"]
        D["<br/><b>vse Engine (C++)</b><br/>Fast, multi-threaded<br/>bytecode execution"]
        E["<br/><b>Simulation Output</b><br/><i>(.csv file & stats)</i>"]
    end

    A -- "vsc model.vs --run" --> B
    B -- Emits --> C
    C -- Consumed by --> D
    D -- Produces --> E

    style A fill:#f9f,stroke:#333,stroke-width:2px
    style B fill:#ccf,stroke:#333,stroke-width:2px
    style D fill:#9f9,stroke:#333,stroke-width:2px
    style C fill:#fff,stroke:#333,stroke-width:1px,stroke-dasharray: 5 5
    style E fill:#fff,stroke:#333,stroke-width:1px,stroke-dasharray: 5 5

Key Features

✨ The ValuaScript Language

  • Intuitive Syntax: A clean, declarative language with a familiar, spreadsheet-like formula syntax.
  • 📦 Code Modularity: Organize models into reusable modules with @import. The compiler resolves the entire dependency graph, including nested and shared ("diamond") dependencies.
  • 🔧 User-Defined Functions: Create reusable, type-safe functions with docstrings, strict lexical scoping, and compile-time recursion detection.
  • 🛡️ Compile-Time Safety: Catch logical errors like type mismatches, incorrect function arguments, undefined variables, and circular imports before you run.
  • 🎲 Integrated Monte Carlo Support: Natively supports a rich library of statistical distributions (Normal, Pert, Lognormal, Beta, etc.).

🚀 The AOT Compiler & C++ Engine

  • High-Performance Backend: A multi-threaded Virtual Machine (VM) written in modern C++17, designed to leverage all available CPU cores for maximum parallelism.
  • 🧠 Intelligent AOT Compiler: Performs all semantic analysis and optimization before execution, generating a low-level JSON bytecode for the engine.
  • ⚙️ Advanced Optimizations:
    • Function Inlining: User-defined functions are seamlessly inlined, eliminating call overhead.
    • Loop-Invariant Code Motion: Deterministic calculations are automatically identified and run only once.
    • Dead Code Elimination: Unused variables are stripped from the final bytecode.

⚡ The VS Code Extension

  • Live Value Preview: Hover over any variable to see its calculated value instantly. For stochastic variables, the engine runs a sample simulation in the background and displays the mean.
  • Real-Time Diagnostics: Get immediate, as-you-type feedback on errors.
  • Hover-for-Help: See full signatures and docstrings for all built-in and user-defined functions.
  • Go-to-Definition: Seamlessly navigate to the source of any user-defined function.

Quick Start: Installation

Get started in minutes with our automated installation scripts.

Prerequisites

  1. Python 3.9+ must be installed and available in your PATH.
  2. Administrator/sudo privileges are required to add the tools to your system's PATH.

macOS & Linux

Open your terminal and run the following one-line command:

/bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Alessio2704/monte-carlo-simulator/main/scripts/install.sh)"

Windows

Open a new PowerShell terminal as Administrator and run:

Set-ExecutionPolicy Bypass -Scope Process -Force; [System.Net.ServicePointManager]::SecurityProtocol = [System.Net.ServicePointManager]::SecurityProtocol -bor 3072; iex ((New-Object System.Net.WebClient).DownloadString('https://raw.githubusercontent.com/Alessio2704/monte-carlo-simulator/main/scripts/install.ps1'))

After installation, you must open a new terminal window for the changes to take effect.

Uninstalling

The same scripts can be used to uninstall the tools. Simply replace install.sh with uninstall.sh (for Mac/Linux) or install.ps1 with uninstall.ps1 (for Windows) in the commands above.

ValuaScript Language Guide

Directives & The Compiler
  • @iterations = <number>: (Required) Defines the number of Monte Carlo trials to run.
  • @output = <variable>: (Required) Specifies which variable's final value should be collected.
  • @output_file = "<path>": (Optional) Exports all trial results to a CSV file.
  • @module: Declares a file as a module containing only func definitions.
  • @import "<path>": Imports all functions from a module file.

The compiler vsc can be invoked with several flags:

  • --run: Compiles and then immediately executes the simulation.
  • -O or --optimize: Enables Dead Code Elimination.
  • --plot: Automatically generates a histogram of the simulation output.
  • -v or --verbose: Provides detailed feedback on the compiler's optimization process.
Variables, Expressions, and Conditionals
# --- Variable Assignment (`let`) ---

# Literals (with numeric separators for readability)
let tax_rate = 0.21
let initial_investment = 1_500_000

# Infix Expressions
let cost_of_equity = risk_free_rate + beta * equity_risk_premium

# Vector Literals
let margin_forecast = [0.25, 0.26, 0.28]

# --- Element Access ---
let my_vector = [100, 200, 300]
let first_element = my_vector[0]   # Accesses the first element (100)
let last_element = my_vector[-1]    # Accesses the last element (300)

# To create a new vector with an element removed, use the `delete_element` function.
let vector_without_last = delete_element(my_vector, -1) # Returns a new vector [100, 200]

# --- Conditional Logic (if/then/else) ---
let tax_regime = if is_high_income then 0.40 else 0.25
User-Defined Functions (UDFs) & Modules

ValuaScript supports fully type-checked, user-defined functions with several key features:

  • Strict Scoping: Functions can only access their own parameters and locally defined variables. They cannot access global variables, ensuring pure, predictable behavior.
  • Compile-Time Validation: The compiler checks for correct types, argument counts, and prevents recursion.
  • Optimization: Functions are inlined by the compiler to eliminate call overhead.
# Define a reusable function with typed parameters and a return type
func calculate_cogs(sales: vector, gross_margin: scalar) -> vector {
    """Calculates Cost of Goods Sold from a sales vector and a margin."""
    let cogs_vector = sales * (1 - gross_margin)
    return cogs_vector
}

# --- Main script body ---
@iterations = 1000
@output = final_cogs

let revenue = grow_series(1000, 0.1, 5)
let margin = Normal(0.4, 0.05)

# Call the user-defined function
let final_cogs = calculate_cogs(revenue, margin)
Built-in Function Reference

A comprehensive library of built-in functions is available for math, series manipulation, I/O, and statistical sampling.

Category Functions
Core log, log10, exp, sin, cos, tan, identity
Series grow_series, compound_series, interpolate_series, sum_series, series_delta, npv, get_element, delete_element, compose_vector, capitalize_expense
Statistics Normal, Lognormal, Beta, Uniform, Bernoulli, Pert, Triangular
Data I/O read_csv_scalar, read_csv_vector
Financial BlackScholes

Contributing: How to Extend ValuaScript

Contributions are welcome! The architecture is designed to make adding new functions—or even entirely new domains of science and engineering—as simple and safe as possible.

Tutorial: How to Add a New Function to an Existing Domain

Let's walk through adding a function named NewFunction to the financial domain.

Step 1: Create the C++ Header (.h) Define your operation's class interface. The build system will find this automatically. File: engine/include/engine/functions/financial/NewFunction.h

#pragma once
#include "include/engine/core/IExecutable.h"

class NewFunctionOperation : public IExecutable
{
public:
    TrialValue execute(const std::vector<TrialValue> &args) const override;
};

Step 2: Create the C++ Implementation (.cpp) Implement the function's logic and its local registration function. The build system will also find this file automatically. File: engine/src/engine/functions/financial/NewFunction.cpp

#include "include/engine/functions/financial/NewFunction.h"
#include "include/engine/functions/FunctionRegistry.h"

// Local registrar for this specific function
void register_new_function_operation(FunctionRegistry& registry)
{
    registry.register_function("NewFunction", []{
        return std::make_unique<NewFunctionOperation>();
    });
}

TrialValue NewFunctionOperation::execute(const std::vector<TrialValue> &args) const
{
    // Your high-performance C++ logic goes here...
    return 42.0;
}

Step 3: Update the Private Domain Manifest Declare your new function's registrar in the domain's internal manifest. This tells the domain it has a new member. File: engine/src/engine/functions/financial/financial_registration.h

#pragma once
class FunctionRegistry;

// Declare all registrars for this domain
void register_black_scholes_operation(FunctionRegistry& registry);
void register_new_function_operation(FunctionRegistry& registry); // Add this line

Step 4: Update the Domain Orchestrator Call your new registrar from the domain's main orchestrator file. This plugs the new function into the domain. File: engine/src/engine/functions/financial/financial.cpp

#include "include/engine/functions/financial/financial.h"
#include "engine/src/engine/functions/financial/financial_registration.h"
#include "include/engine/functions/FunctionRegistry.h"

void register_financial_functions(FunctionRegistry& registry)
{
    register_black_scholes_operation(registry);
    register_new_function_operation(registry); // Add this line
}

Step 5: Define the Python Signature Finally, teach the compiler about your new function. The dynamic loader will find this automatically. File: compiler/vsc/functions/financial.py

SIGNATURES = {
    "BlackScholes": { ... },
    "NewFunction": {
        "variadic": False,
        "arg_types": ["scalar", "vector"], # Example arguments
        "return_type": "scalar",
        "is_stochastic": False,
        "doc": {
            "summary": "A brief description of what NewFunction does.",
            "params": [
                {"name": "param1", "desc": "Description of the first parameter."},
                {"name": "param2", "desc": "Description of the second parameter."}
            ],
            "returns": "Description of the return value.",
        },
    },
}

Step 6: Add Tests! A pull request must include tests. Add a new test file engine/test/functions/financial/test_new_function.cpp and update the CMakeLists.txt to include it.

Your function is now fully integrated!

Tutorial: How to Add a Completely New Domain

If your function belongs to a new field (e.g., epidemiology, physics), you should first create a new domain. Let's add an epidemiology domain.

Step 1: Create the Domain Directory Structure Create folders for your new domain in both the engine source and header directories.

engine/include/engine/functions/epidemiology/
engine/src/engine/functions/epidemiology/

Step 2: Create the Public Domain Header This is the clean, public-facing header for the entire domain. File: engine/include/engine/functions/epidemiology/epidemiology.h

#pragma once
class FunctionRegistry;
void register_epidemiology_functions(FunctionRegistry& registry);

Step 3: Create the Domain Orchestrator and Private Manifest These files will organize and register all functions within the new domain. Initially, they will be empty placeholders. File: engine/src/engine/functions/epidemiology/epidemiology_registration.h

#pragma once
class FunctionRegistry;
// This file will list all individual function registrars for this domain.

File: engine/src/engine/functions/epidemiology/epidemiology.cpp

#include "include/engine/functions/epidemiology/epidemiology.h"
#include "engine/src/engine/functions/epidemiology/epidemiology_registration.h"
#include "include/engine/functions/FunctionRegistry.h"

void register_epidemiology_functions(FunctionRegistry& registry)
{
    // This function will call all registrars from the private header.
}

Step 4: Register the New Domain with the Engine This is the only time you will edit the core engine code. Add your new domain's registration function to the main list. File: engine/src/engine/core/SimulationEngine.cpp

// ... inside SimulationEngine::build_function_registry() ...
#include "include/engine/functions/financial/financial.h"
#include "include/engine/functions/epidemiology/epidemiology.h" // Add this line

void SimulationEngine::build_function_registry()
{
    // ...
    register_financial_functions(*m_function_registry);
    register_epidemiology_functions(*m_function_registry); // Add this line
    // ...
}

Step 5: Create the Python Signature File The compiler's dynamic loader will automatically discover this new file. File: compiler/vsc/functions/epidemiology.py

"""
Signatures for epidemiological modeling functions.
"""
SIGNATURES = {
    # Initially empty. Add function signatures here.
}

Your new domain is now set up! From here, you can follow the guide "How to Add a New Function" to populate your epidemiology domain with models like SIR, SEIR, etc.

Running the Test Suite

A pull request must include tests for any new functionality.

1. C++ Engine Tests (GoogleTest & CTest)

From the root directory of the project:

# Configure and build the project
cmake -S . -B build
cmake --build build

# Run the full test suite
cd build
ctest --verbose

2. Python Compiler Tests (Pytest)

# Navigate to the compiler directory
cd compiler

# (Recommended) Create and activate a virtual environment
python3 -m venv venv
source venv/bin/activate  # On Windows: .\venv\Scripts\activate

# Install the compiler and its development dependencies
pip install -e .[dev]

# Run the tests
pytest -v

Roadmap

  • Empirical Distribution Sampling: Add a function to create a custom sampler from a real-world data series (e.g., historical stock returns), allowing models to be driven by actual data instead of theoretical distributions.
  • GPU Acceleration: Explore CUDA/OpenCL to offload the "embarrassingly parallel" Monte Carlo workload to the GPU, providing an order-of-magnitude performance increase for simulations with millions of trials.

License

This project is distributed under the MIT License. See the LICENSE file for more information.

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

valuascript_compiler-3.1.2.tar.gz (61.8 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

valuascript_compiler-3.1.2-py3-none-any.whl (40.9 kB view details)

Uploaded Python 3

File details

Details for the file valuascript_compiler-3.1.2.tar.gz.

File metadata

  • Download URL: valuascript_compiler-3.1.2.tar.gz
  • Upload date:
  • Size: 61.8 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.1.0 CPython/3.13.7

File hashes

Hashes for valuascript_compiler-3.1.2.tar.gz
Algorithm Hash digest
SHA256 8eb181a3aceb40e65eae3848e7c0fe34c21339f878a66795a83fa193b0d9be14
MD5 302c9874b5beb4bb8930593d73a847d9
BLAKE2b-256 d9e15f67b63c360239c6fba4a9429d306208813468d4473fbe4edc7054786063

See more details on using hashes here.

File details

Details for the file valuascript_compiler-3.1.2-py3-none-any.whl.

File metadata

File hashes

Hashes for valuascript_compiler-3.1.2-py3-none-any.whl
Algorithm Hash digest
SHA256 e47ca590579ef5d29f05693029ea86a0e2f3aa75fb8b23c305f5dba19dc18bdf
MD5 f104a05a882ec6cc90bbf51c92b34f3f
BLAKE2b-256 c953e2972a2b0b8b5b8645c2d090f9f01504580ef564c5a7919f898a11801a10

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page