mirror of
https://github.com/SHOGGOTH-SECTOR/sica-fondt.git
synced 2026-08-01 16:40:24 +00:00
M3d mev/main.f90: PGA all-pay auction simulation with bounded rationality, 0-1 knapsack block builder (dynamic programming), WENO5 shock-capturing PDE solver for adversarial dynamics. M3e tokenomics/main.f90: Euler-Maruyama SDE solver for token supply trajectories, stock-flow conservation (L2 invariant), kinked lending rate (L3: kink at U_opt, Aave-style), liquidation cascade detection, halving events as drift discontinuities. M3g microstructure/main.zig: Order book with spread/depth, non-linear slippage estimation (L2: function of order size), Almgren-Chriss optimal execution via Riccati (sinh/cosh trajectory), BoundedPrediction with invariant enforcement. Fix: M3b sociological sim_type was hardcoded as 'statistical' instead of 'sociological'.
259 lines
8.6 KiB
Zig
259 lines
8.6 KiB
Zig
// M3g -- Market microstructure sims
|
|
// Language: Zig (tick-level latency, deterministic memory layout)
|
|
// Protocol: line-delimited JSON on stdin/stdout to hub.tcl
|
|
|
|
const std = @import("std");
|
|
const math = std.math;
|
|
|
|
// -- Hyperbolic functions --------------------------------------------------
|
|
fn sinh(x: f64) f64 {
|
|
return (@exp(x) - @exp(-x)) / 2.0;
|
|
}
|
|
fn cosh(x: f64) f64 {
|
|
return (@exp(x) + @exp(-x)) / 2.0;
|
|
}
|
|
fn tanh(x: f64) f64 {
|
|
return sinh(x) / cosh(x);
|
|
}
|
|
|
|
// -- BoundedPrediction (L2: every output has explicit bounds) -------------
|
|
|
|
const BoundedPrediction = struct {
|
|
value: f64,
|
|
lower_bound: f64,
|
|
upper_bound: f64,
|
|
confidence: f64,
|
|
time_horizon: []const u8,
|
|
sim_type: []const u8 = "market_microstructure",
|
|
|
|
fn init(value: f64, lower: f64, upper: f64, confidence: f64, horizon: []const u8) !BoundedPrediction {
|
|
if (lower > value or value > upper)
|
|
return error.BoundsViolation;
|
|
if (confidence < 0.0 or confidence > 10.0)
|
|
return error.ConfidenceOutOfRange;
|
|
return .{
|
|
.value = value,
|
|
.lower_bound = lower,
|
|
.upper_bound = upper,
|
|
.confidence = confidence,
|
|
.time_horizon = horizon,
|
|
};
|
|
}
|
|
};
|
|
|
|
// -- Order book -----------------------------------------------------------
|
|
|
|
const Side = enum { bid, ask };
|
|
|
|
const Order = struct {
|
|
price: f64,
|
|
quantity: f64,
|
|
side: Side,
|
|
id: u64,
|
|
};
|
|
|
|
const MAX_LEVELS: usize = 256;
|
|
|
|
const OrderBook = struct {
|
|
bids: [MAX_LEVELS]Order,
|
|
asks: [MAX_LEVELS]Order,
|
|
n_bids: usize,
|
|
n_asks: usize,
|
|
mid_price: f64,
|
|
|
|
fn init() OrderBook {
|
|
return .{
|
|
.bids = undefined,
|
|
.asks = undefined,
|
|
.n_bids = 0,
|
|
.n_asks = 0,
|
|
.mid_price = 0.0,
|
|
};
|
|
}
|
|
|
|
fn addOrder(self: *OrderBook, order: Order) void {
|
|
switch (order.side) {
|
|
.bid => {
|
|
if (self.n_bids < MAX_LEVELS) {
|
|
self.bids[self.n_bids] = order;
|
|
self.n_bids += 1;
|
|
}
|
|
},
|
|
.ask => {
|
|
if (self.n_asks < MAX_LEVELS) {
|
|
self.asks[self.n_asks] = order;
|
|
self.n_asks += 1;
|
|
}
|
|
},
|
|
}
|
|
self.updateMid();
|
|
}
|
|
|
|
fn bestBid(self: *const OrderBook) f64 {
|
|
if (self.n_bids == 0) return 0.0;
|
|
var best: f64 = 0.0;
|
|
for (self.bids[0..self.n_bids]) |b| {
|
|
if (b.price > best) best = b.price;
|
|
}
|
|
return best;
|
|
}
|
|
|
|
fn bestAsk(self: *const OrderBook) f64 {
|
|
if (self.n_asks == 0) return math.inf(f64);
|
|
var best: f64 = math.inf(f64);
|
|
for (self.asks[0..self.n_asks]) |a| {
|
|
if (a.price < best) best = a.price;
|
|
}
|
|
return best;
|
|
}
|
|
|
|
fn spread(self: *const OrderBook) f64 {
|
|
return self.bestAsk() - self.bestBid();
|
|
}
|
|
|
|
fn updateMid(self: *OrderBook) void {
|
|
const bb = self.bestBid();
|
|
const ba = self.bestAsk();
|
|
if (bb > 0.0 and ba < math.inf(f64)) {
|
|
self.mid_price = (bb + ba) / 2.0;
|
|
}
|
|
}
|
|
|
|
// L2: slippage is a function of order size and current depth (non-linear)
|
|
fn estimateSlippage(self: *const OrderBook, size: f64, side: Side) f64 {
|
|
var remaining = size;
|
|
var cost: f64 = 0.0;
|
|
const ref_price = self.mid_price;
|
|
|
|
switch (side) {
|
|
.bid => {
|
|
// Buying: walk up the ask side
|
|
var i: usize = 0;
|
|
while (i < self.n_asks and remaining > 0.0) : (i += 1) {
|
|
const fill = @min(remaining, self.asks[i].quantity);
|
|
cost += fill * self.asks[i].price;
|
|
remaining -= fill;
|
|
}
|
|
},
|
|
.ask => {
|
|
// Selling: walk down the bid side
|
|
var i: usize = 0;
|
|
while (i < self.n_bids and remaining > 0.0) : (i += 1) {
|
|
const fill = @min(remaining, self.bids[i].quantity);
|
|
cost += fill * self.bids[i].price;
|
|
remaining -= fill;
|
|
}
|
|
},
|
|
}
|
|
|
|
if (size <= remaining) return 0.0;
|
|
const avg_price = cost / (size - remaining);
|
|
return @abs(avg_price - ref_price) / ref_price;
|
|
}
|
|
};
|
|
|
|
// -- Almgren-Chriss optimal execution -------------------------------------
|
|
// min integral [lambda * x(t) * dx/dt + eta * (dx/dt)^2] dt
|
|
// Solution via Riccati: x(t) = X * sinh(kappa*(T-t)) / sinh(kappa*T)
|
|
// kappa = sqrt(lambda / eta)
|
|
|
|
const AlmgrenChriss = struct {
|
|
lambda: f64, // permanent impact
|
|
eta: f64, // temporary impact
|
|
sigma: f64, // volatility (for timing risk)
|
|
risk_aversion: f64,
|
|
|
|
fn optimalTrajectory(self: *const AlmgrenChriss, total_shares: f64, T: f64, n_buckets: usize, schedule: []f64) void {
|
|
const kappa = @sqrt(self.risk_aversion * self.sigma * self.sigma / self.eta);
|
|
const sinh_kT = sinh(kappa * T);
|
|
const dt = T / @as(f64, @floatFromInt(n_buckets));
|
|
|
|
var prev_x = total_shares;
|
|
for (0..n_buckets) |i| {
|
|
const t = @as(f64, @floatFromInt(i + 1)) * dt;
|
|
const x_t = total_shares * sinh(kappa * (T - t)) / sinh_kT;
|
|
schedule[i] = (prev_x - x_t) / total_shares;
|
|
prev_x = x_t;
|
|
}
|
|
}
|
|
|
|
fn executionCost(self: *const AlmgrenChriss, total_shares: f64, T: f64) f64 {
|
|
const kappa = @sqrt(self.risk_aversion * self.sigma * self.sigma / self.eta);
|
|
return self.eta * total_shares * total_shares * kappa / tanh(kappa * T);
|
|
}
|
|
};
|
|
|
|
// -- Self-test ------------------------------------------------------------
|
|
|
|
pub fn main() !void {
|
|
const stdout = std.io.getStdOut().writer();
|
|
|
|
try stdout.print("M3g Market Microstructure Sim -- Zig {s}\n\n", .{@tagName(std.Target.Os.Tag.linux)});
|
|
|
|
// Order book test
|
|
try stdout.print("Order book (spread, slippage):\n", .{});
|
|
var book = OrderBook.init();
|
|
book.addOrder(.{ .price = 1800.0, .quantity = 5.0, .side = .bid, .id = 1 });
|
|
book.addOrder(.{ .price = 1799.0, .quantity = 10.0, .side = .bid, .id = 2 });
|
|
book.addOrder(.{ .price = 1798.0, .quantity = 20.0, .side = .bid, .id = 3 });
|
|
book.addOrder(.{ .price = 1801.0, .quantity = 5.0, .side = .ask, .id = 4 });
|
|
book.addOrder(.{ .price = 1802.0, .quantity = 10.0, .side = .ask, .id = 5 });
|
|
book.addOrder(.{ .price = 1805.0, .quantity = 20.0, .side = .ask, .id = 6 });
|
|
|
|
try stdout.print(" best bid: {d:.2} best ask: {d:.2}\n", .{ book.bestBid(), book.bestAsk() });
|
|
try stdout.print(" spread: {d:.2}\n", .{book.spread()});
|
|
|
|
const slip_small = book.estimateSlippage(3.0, .bid);
|
|
const slip_large = book.estimateSlippage(20.0, .bid);
|
|
try stdout.print(" slippage (3 ETH buy): {d:.6}\n", .{slip_small});
|
|
try stdout.print(" slippage (20 ETH buy): {d:.6}\n", .{slip_large});
|
|
|
|
// L2: larger orders produce greater slippage
|
|
if (slip_large > slip_small) {
|
|
try stdout.print(" L2 non-linear slippage: PASS\n\n", .{});
|
|
} else {
|
|
try stdout.print(" L2 non-linear slippage: FAIL\n\n", .{});
|
|
}
|
|
|
|
// Almgren-Chriss test
|
|
try stdout.print("Almgren-Chriss optimal execution:\n", .{});
|
|
const ac = AlmgrenChriss{
|
|
.lambda = 0.001,
|
|
.eta = 0.01,
|
|
.sigma = 0.02,
|
|
.risk_aversion = 1.0e-6,
|
|
};
|
|
|
|
var schedule: [5]f64 = undefined;
|
|
ac.optimalTrajectory(100.0, 30.0, 5, &schedule);
|
|
|
|
try stdout.print(" schedule (5 buckets, 100 shares, 30 min):\n", .{});
|
|
for (schedule, 0..) |s, i| {
|
|
try stdout.print(" bucket {d}: {d:.4}\n", .{ i + 1, s });
|
|
}
|
|
|
|
const cost = ac.executionCost(100.0, 30.0);
|
|
try stdout.print(" total cost: {d:.4}\n\n", .{cost});
|
|
|
|
// BoundedPrediction test
|
|
try stdout.print("BoundedPrediction:\n", .{});
|
|
const bp = try BoundedPrediction.init(0.0034, 0.0018, 0.0052, 8.50, "next_trade");
|
|
try stdout.print(" slippage: {d:.4} [{d:.4}, {d:.4}]\n", .{ bp.value, bp.lower_bound, bp.upper_bound });
|
|
try stdout.print(" confidence: {d:.2}/10.00\n\n", .{bp.confidence});
|
|
|
|
// Invariant checks
|
|
try stdout.print("Invariant checks:\n", .{});
|
|
if (BoundedPrediction.init(5.0, 6.0, 8.0, 7.0, "1h")) |_| {
|
|
try stdout.print(" L2 bounds: FAIL\n", .{});
|
|
} else |_| {
|
|
try stdout.print(" L2 bounds: PASS (rejected lower > value)\n", .{});
|
|
}
|
|
if (BoundedPrediction.init(5.0, 4.0, 8.0, 11.0, "1h")) |_| {
|
|
try stdout.print(" Confidence range: FAIL\n", .{});
|
|
} else |_| {
|
|
try stdout.print(" Confidence range: PASS (rejected 11.0 > 10.0)\n", .{});
|
|
}
|
|
|
|
try stdout.print("\nAll models operational.\n", .{});
|
|
}
|