@moneolabs/trading
Execution for AI agents: quotes, slippage bounds, TWAP and bracket orders, positions.
npm install @moneolabs/trading
An agent that can only pay is half a participant. This package gives it quotes, routing, and order
types that survive being left alone overnight, with the spend guard sitting between every intent
and every signature.
Use
import { createTrading, simulatedVenue } from "@moneolabs/trading";
const trading = createTrading({
venues: [simulatedVenue({ prices: { "USDG/AAPL": 1 / 309.92 } })],
quoteAsset: "USDG",
guard,
});
const quote = await trading.quote({ sell: "USDG", buy: "AAPL", notional: "1500 USDG" });
const order = await trading.execute(quote, { type: "twap", window: "30m", maxSlippage: 0.003 });
const result = await order.settled();
result.status;
result.averagePrice;
result.slippage;
Routing
quote() asks every venue that can trade the pair and returns whichever gives back the most of the
buy asset. Not the lowest fee, not the best headline price: the most units, after fees and impact.
await trading.quoteAll({ sell: "USDG", buy: "AAPL", notional: "1500 USDG" });
Order types
market | Fills at once inside the slippage bound. Rejected, not filled worse, if the bound is missed. |
limit | Rests until the price arrives or expiresIn passes. |
twap | Slices the notional across window, so a large order stops being the reason the price moved. |
bracket | Enters at once with takeProfit and stopLoss recorded at submission. |
TWAP slices run on absolute deadlines, not "wait gap after the last fill". Filling takes time,
and with relative sleeps that time is added to every remaining slice until the order runs past the
window it was given.
const order = await trading.execute(quote, { type: "twap", window: "30m", slices: 6 });
order.status();
await order.cancel();
Slippage
Every order carries a bound. It is checked twice: against a fresh quote before the venue is touched,
and against the achieved fill price after. Exceeding it rejects rather than filling worse.
const result = await (await trading.execute(quote, { maxSlippage: 0.003 })).settled();
result.status;
result.reason;
result.fills;
A rejected order never reaches a venue, so it costs nothing. Under a guard, the reserved budget is
released; a partially filled order settles for exactly what filled.
Positions
Cost basis moves with average cost, so selling half a position realizes half the gain.
await (await trading.execute(entry)).settled();
trading.position("AAPL");
trading.mark("AAPL", 317.40);
await trading.close("AAPL");
trading.realized();
Cost and proceeds have to land in the quote asset. Dollar-pegged assets convert by precision alone.
Anything else needs a price, and silently guessing one is how a P&L number becomes fiction, so it
is refused instead.
Venues
Venue is an interface with two methods, quote and fill. simulatedVenue is the reference
implementation: a fixed price table, configurable fees, price impact, and drift.
simulatedVenue({
prices: { "USDG/AAPL": 1 / 309.92 },
feeRate: 0.0005,
impactPerUnit: 0.00001,
drift: 0.02,
quoteTtlMs: 12_000,
});
Nothing in it is random. Given the same inputs it produces the same fills, which is the only way an
execution test is worth running. Turn drift up to watch the slippage bound do its job.
Testing
Pass a manualClock and a thirty minute TWAP takes a millisecond.
import { manualClock } from "@moneolabs/core";
const clock = manualClock(0);
const trading = createTrading({ venues, clock });
const order = await trading.execute(quote, { type: "twap", window: "30m", slices: 6 });
await clock.advance("31m");
(await order.settled()).fills;
License
MIT