Generic, exchange-agnostic paper-trading and simulated-execution engine for Elixir.
PaperEx is a pure functional core: plain structs and state
transitions for portfolios, positions, orders, and execution
attempts. There is no HTTP, no WebSocket, no database, and no
exchange dependency. Consumers persist state however they like and
connect their own market data through an adapter behaviour.
What's here
PaperEx.Engine— the pure execution engine.apply_order/4takes a portfolio, an order, a market snapshot, and a config and returns the updated portfolio plus anExecutionrecord. It also owns the pending-remainder lifecycle (advance_pending/3,cancel_pending/3,open_pending_remainders/1) and market resolution (resolve_market/4).PaperEx.Adapter— the behaviour an exchange adapter implements to translate exchange-specific data into normalized structs and simulate fills, keeping the engine exchange-agnostic.- Domain structs —
PaperEx.Order,PaperEx.Execution,PaperEx.Position,PaperEx.Portfolio,PaperEx.Fill,PaperEx.Instrument,PaperEx.MarketSnapshot. PaperEx.Valuation— pure mark-to-market of open positions against caller-supplied snapshots.PaperEx.Strategy— the behaviour for pure decision functions.PaperEx.ReasonCodes— the bounded set of generic engine reason codes.
Two simulation modes — design constraint
The package was extracted from a Polymarket prediction-market trading bot that ran two paper layers on purpose:
- Research / idea simulation. Idealized fills, simplified exits. Answers "was the signal directionally good?".
- Live-mirror simulation. Realistic execution constraints — entry caps, slippage, GTC pending lifecycle, missed fills, skipped attempts. Answers "what would live execution have done?".
The engine keeps these modes separable through configuration, not
separate code paths (see PaperEx.Engine). It shows up in
PaperEx.Execution: :filled, :missed, :skipped, :pending,
:cancelled, and :closed are peer status values, so a missed fill
is recorded with the same first-class shape as a successful fill
rather than being absorbed into "no trade".
Out of scope
PaperEx is not a complete trading platform. It does not own
exchange specifics (Polymarket token/condition IDs, Gamma, CLOB, and
RTDS live in polymarket_sdk), HTTP/WebSocket clients, Ecto schemas,
Phoenix, or strategy rules. A ready-made Polymarket adapter lives in
the paper_ex_polymarket package.
Summary
Functions
@spec version() :: String.t()
Returns the package version, read from the loaded application spec
(so it always tracks mix.exs).