/// @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
}
}