shape-runtime 0.3.2

Bytecode compiler, builtins, and runtime infrastructure for Shape
Documentation
/// @module std::finance::backtest::fills
/// Order Fill Simulation
///
/// Models for realistic order fill simulation including slippage,
/// commissions, and market impact.

// ===== Slippage Models =====

/// Fixed slippage model configuration
/// Applies a constant slippage in basis points
pub type FixedSlippage = {
    model_type: string;     // "fixed"
    bps: number;            // Slippage in basis points (1 bp = 0.01%)
};

/// Percentage-based slippage model
/// Slippage as percentage of price
pub type PercentageSlippage = {
    model_type: string;     // "percentage"
    pct: number;            // Slippage as percentage (e.g., 0.1 = 0.1%)
};

/// Volume impact slippage model (square-root market impact)
/// Impact = coefficient * sqrt(volume / avg_volume)
pub type VolumeImpactSlippage = {
    model_type: string;     // "volume_impact"
    coefficient: number;    // Impact coefficient
    avg_volume: number;     // Average daily volume for reference
};

/// Create fixed slippage model
/// @param bps - Slippage in basis points (e.g., 5 = 0.05%)
pub fn fixed_slippage(bps = 5.0) {
    {
        model_type: "fixed",
        bps: bps
    }
}

/// Create percentage slippage model
/// @param pct - Slippage as percentage (e.g., 0.1 = 0.1%)
pub fn percentage_slippage(pct = 0.1) {
    {
        model_type: "percentage",
        pct: pct
    }
}

/// Create volume impact slippage model
/// @param coefficient - Impact coefficient (typically 0.1 to 0.5)
/// @param avg_volume - Average daily volume
pub fn volume_impact_slippage(coefficient = 0.1, avg_volume = 1000000.0) {
    {
        model_type: "volume_impact",
        coefficient: coefficient,
        avg_volume: avg_volume
    }
}

/// No slippage model (for testing)
pub fn no_slippage() {
    {
        model_type: "fixed",
        bps: 0.0
    }
}

// ===== Slippage Calculation =====

/// Calculate fill price with slippage applied
/// @param price - Base execution price
/// @param side - "buy" or "sell"
/// @param volume - Order volume (used for volume impact model)
/// @param model - Slippage model configuration
pub fn apply_slippage(price, side, volume, model) {
    let slip = 0.0;

    if model.model_type == "fixed" {
        // Fixed basis points
        slip = price * model.bps / 10000.0;
    } else if model.model_type == "percentage" {
        // Percentage of price
        slip = price * model.pct / 100.0;
    } else if model.model_type == "volume_impact" {
        // Square-root market impact
        let volume_ratio = volume / model.avg_volume;
        slip = price * model.coefficient * sqrt(volume_ratio) / 100.0;
    }

    // Buy orders get worse (higher) price, sell orders get worse (lower) price
    if side == "buy" {
        price + slip
    } else {
        price - slip
    }
}

// ===== Commission Models =====

/// Commission configuration
pub type Commission = {
    model_type: string;     // "fixed" | "percentage" | "per_share" | "tiered"
    fixed_amount: number;   // Fixed commission per trade
    pct: number;            // Percentage of trade value
    per_share: number;      // Commission per share
    min_commission: number; // Minimum commission
    max_commission: number; // Maximum commission (0 = no max)
};

/// Create fixed commission model
/// @param amount - Fixed commission per trade
pub fn fixed_commission(amount = 0.0) {
    {
        model_type: "fixed",
        fixed_amount: amount,
        pct: 0.0,
        per_share: 0.0,
        min_commission: 0.0,
        max_commission: 0.0
    }
}

/// Create percentage-based commission
/// @param pct - Commission as percentage of trade value (e.g., 0.1 = 0.1%)
/// @param min_commission - Minimum commission per trade
pub fn percentage_commission(pct = 0.1, min_commission = 0.0) {
    {
        model_type: "percentage",
        fixed_amount: 0.0,
        pct: pct,
        per_share: 0.0,
        min_commission: min_commission,
        max_commission: 0.0
    }
}

/// Create per-share commission
/// @param per_share - Commission per share (e.g., 0.005)
/// @param min_commission - Minimum commission per trade
/// @param max_commission - Maximum commission per trade (0 = no max)
pub fn per_share_commission(per_share = 0.005, min_commission = 1.0, max_commission = 0.0) {
    {
        model_type: "per_share",
        fixed_amount: 0.0,
        pct: 0.0,
        per_share: per_share,
        min_commission: min_commission,
        max_commission: max_commission
    }
}

/// No commission (for testing)
pub fn no_commission() {
    fixed_commission(0.0)
}

/// Calculate commission for a trade
/// @param price - Execution price
/// @param quantity - Number of shares/contracts
/// @param model - Commission model configuration
pub fn calculate_commission(price, quantity, model) {
    let comm = 0.0;

    if model.model_type == "fixed" {
        comm = model.fixed_amount;
    } else if model.model_type == "percentage" {
        comm = price * quantity * model.pct / 100.0;
    } else if model.model_type == "per_share" {
        comm = quantity * model.per_share;
    }

    // Apply min/max constraints
    if comm < model.min_commission {
        comm = model.min_commission;
    }
    if model.max_commission > 0.0 && comm > model.max_commission {
        comm = model.max_commission;
    }

    comm
}

// ===== Fill Simulation =====

/// Complete fill result
pub type FillResult = {
    filled: bool;           // Whether order was filled
    fill_price: number;     // Actual fill price (with slippage)
    fill_quantity: number;  // Filled quantity
    commission: number;     // Commission paid
    total_cost: number;     // Total cost including commission
};

/// Simulate filling an order
/// @param order - Order to fill
/// @param candle - Current candle for price reference
/// @param slippage_model - Slippage configuration
/// @param commission_model - Commission configuration
pub fn simulate_fill(order, candle, slippage_model, commission_model) {
    // For market orders, use close price as base
    // For limit orders, check if price is achievable
    let base_price = candle.close;
    let can_fill = true;

    if order.order_type == "limit" {
        if order.side == "buy" {
            // Buy limit: only fill if price went low enough
            can_fill = candle.low <= order.price;
            if can_fill {
                base_price = order.price;
            }
        } else {
            // Sell limit: only fill if price went high enough
            can_fill = candle.high >= order.price;
            if can_fill {
                base_price = order.price;
            }
        }
    } else if order.order_type == "stop" {
        if order.side == "buy" {
            // Buy stop: fill when price goes above stop
            can_fill = candle.high >= order.price;
            if can_fill {
                base_price = order.price;
            }
        } else {
            // Sell stop: fill when price goes below stop
            can_fill = candle.low <= order.price;
            if can_fill {
                base_price = order.price;
            }
        }
    }

    if !can_fill {
        return {
            filled: false,
            fill_price: 0.0,
            fill_quantity: 0.0,
            commission: 0.0,
            total_cost: 0.0
        };
    }

    // Apply slippage
    let fill_price = apply_slippage(base_price, order.side, order.quantity, slippage_model);

    // Calculate commission
    let comm = calculate_commission(fill_price, order.quantity, commission_model);

    // Calculate total cost
    let trade_value = fill_price * order.quantity;
    let total = if order.side == "buy" {
        trade_value + comm
    } else {
        trade_value - comm
    };

    {
        filled: true,
        fill_price: fill_price,
        fill_quantity: order.quantity,
        commission: comm,
        total_cost: total
    }
}