defmodule PaperEx do @moduledoc """ 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/4` takes a portfolio, an order, a market snapshot, and a config and returns the updated portfolio plus an `Execution` record. 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: 1. **Research / idea simulation.** Idealized fills, simplified exits. Answers "was the signal directionally good?". 2. **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. """ @doc """ Returns the package version, read from the loaded application spec (so it always tracks `mix.exs`). """ @spec version() :: String.t() def version, do: Application.spec(:paper_ex, :vsn) |> to_string() end