A gateway between your stack and EPEX M7.
Voltnir is a Rust gateway that holds the EPEX SPOT M7 session for you — AMQP for order entry and private feeds, WebSocket for market data — and exposes it to your own software three ways: REST, a WebSocket push feed, and gRPC. It maintains the order books, session state, reconnects and gap detection; your strategy sends orders and reads the book.
3000.9001.voltnir.api.v1 over HTTP/2 — default port 3443.internal) or external PostgreSQL — selected in config, identical schema either way.8080.What happens when you place an order.
A POST /api/v1/order (or the equivalent gRPC RPC) doesn't go straight to the wire. It runs a fixed validation chain first — every order, every transport, same order of checks. If any gate rejects, nothing reaches the exchange and the reason comes back to you.
Authentication & permission
The caller is authenticated by API token, and must hold the
CreateOrderpermission. No permission, no order — enforced at the gateway boundary, not in your client.Virtual-member resolution
If the order names a
v_member_short_id, that virtual member must exist, be active, and be assigned to the calling user. The short id is carried through to the exchange encoded in the M7 free-text field, so it can be recovered on the acknowledged order and on every fill. A user holdingBypassMemberCheckis the only one who can skip member binding.Unit conversion to fixed-point integers
Human units convert to integers before anything else touches them: price
€/MWh → cents(×100), quantityMW → thousandths of a MW(×1000). There are no floats anywhere past this point — no rounding ghosts in limits, fills, or P&L.Position-check lock
The read-modify-write of the position is taken under a lock keyed to the contract, so two orders submitted at the same instant can't each independently pass the limit and together breach it. The limit check sees a consistent before-state.
Two-sided position-limit check
Exposure is bounded on both sides independently — a
(max_short, max_long)pair, not a single net number. The order's projected after-bounds are checked against the configured limit on the side it grows. You can't hide an oversized buy behind a resting sell: each side is capped on its own.Per-member limit
A virtual member's own
max_positionis enforced separately from the account-wide limit. The order must clear both. This is what lets one EPEX membership host many entities without one of them spending another's headroom.Risk-reducing carve-out
An order that only reduces exposure on an over-limit side is allowed even when you are already over the limit. You can always trade out of a position — the limit blocks growth, never an exit.
Operational-health gate
The order is rejected if trading is disabled by the operator kill-switch, or if the exchange / AMQP / market-data health flags aren't green. No order is sent into a degraded session.
AMQP submit → client_order_id
The order is encoded to the M7 wire shape and published over AMQP. You get a
client_order_idback immediately and the order enters statePENDING— you can watch it from that id straight away.Bounded acknowledgement wait
The gateway waits up to 10 seconds for M7 to acknowledge. On ack the order becomes
ACTIVE; on a reject it becomesREJECTEDwith the exchange reason; if the window elapses with no answer, the timeout is surfaced rather than left hanging.State machine
From there the order is driven by the exchange:
PENDING → ACKNOWLEDGED → ACTIVE, with terminal statesREJECTED,CANCELLED(inactive), andFILLED. Every transition is broadcast on the WebSocket and gRPC order streams.
POST /api/v1/order Authorization: Bearer ${VOLTNIR_TOKEN} Content-Type: application/json { "side": "BUY", "price": 5000, // €50.00 / MWh, as cents "quantity": 1000, // 1.0 MW, as thousandths "delivery_area_id": "10YNL----------L", "product": "H", "delivery_start": "2026-05-15T22:00:00Z", "v_member_short_id": "NL-BATTERY" } // → 201 Created // {"client_order_id":"550e...","state":"PENDING","reason":null} // → on M7 ack the order stream emits state ACTIVE
Two numbers, and they mean different things.
Voltnir tracks your net position (what you actually hold) separately from your exposure (what you could end up holding). The limit checks run on exposure; your P&L runs on the net position. Conflating the two is how desks get surprised.
Net position
Executed trades only — fully matched (ACTI) volume on a contract, signed by side. This is the megawatt position you are actually carrying, and the basis for realized P&L. Available per delivery-area/contract and per virtual member.
Exposure bounds
Executed trades plus your open resting orders (including the hidden quantity of icebergs), expressed as a two-sided (max_short, max_long) pair — the worst case each way if everything resting fills. This is what the position-limit check evaluates.
The carve-out, restated. Because the check is two-sided and compares the projected after-bounds to the before-bounds, an order that only shrinks the breaching side passes even when you're over the limit — and an order that grows it fails. A configured limit of 0 therefore means "no new exposure, reductions only": a hard freeze you can still trade out of.
Kill switches. Trading can be halted outright (trade_enabled = false), which fails the operational-health gate for every new order. Limits can be set per account and per virtual member. Together they're the off-switch the landing page promises — and they live on your box, under your control.
Three transports, one contract.
REST for request/response, WebSocket for a push feed, gRPC for streaming and production. The unary surface is mirrored exactly between REST and gRPC — same fields, same units, same permission gates, same error semantics — so moving from prototype to production is a transport swap, not a rewrite.
REST V1 — :3000
JSON over HTTP. Orders, modify/cancel, contracts, positions, P&L, audit, members and users, kill-switch. Documented endpoint-by-endpoint; prototype it in curl.
WebSocket — :9001
zstd-compressed JSON frames; authenticate with {action:'auth',token} after connect, then subscribe. Always-on streams (orders, trades, messages, status, P&L), per-area contracts, and opt-in public-trade and hub-to-hub feeds.
gRPC — :3443
Service voltnir.api.v1 over HTTP/2, protobuf-framed. Unary RPCs mirror REST one-for-one; server-streaming RPCs push book deltas, order events and fills. One .proto is the contract.
WebSocket streams. The push feed carries contracts, orders, trades, public_trades, trade_tape, messages, pnl, status, and state. Orders, trades, messages, status and P&L are on by default once authenticated; contracts are per area/product; public trades, the trade tape and the hub-to-hub matrix are opt-in.
Numbers behave like numbers. Every price, quantity and limit crosses the wire as a fixed-point integer — no floats anywhere in the API. Keys are hashed at rest. API keys are SHA-256 hashed and shown exactly once at creation; lose one, you rotate it. A first-party Python SDK wrapping the gRPC service ships in-tree, generated from the same .proto.
Virtual members, named users, composable permissions.
One EPEX membership can host many virtual members — each with a short id, an active flag, and its own max_position. Users are assigned to members and hold a set of permissions, checked at the boundary on every action. Eleven permissions compose the access model:
A user with CreateOrder but not DeleteOrder can place orders and not cancel them — not their own, not anyone's. Trading entitlements (CreateOrder / ModifyOrder / DeleteOrder) are separate from operational ones (ToggleTrading, SetPositionLimit, RestartSystem) and from administrative ones (ManageUsers, ManageMembers). BypassMemberCheck is the deliberate exception that lets a privileged user act without virtual-member binding.
Attribution travels with every action: which user, which virtual member, which EPEX user_id, recovered even on the exchange acknowledgement because the member tag rides in the M7 free-text field.
Every order and trade, stored on your box.
Orders, trades and public trades are persisted to your chosen backend and queryable through the REST audit endpoints — filter by user, member, event and time range; export as CSV or JSON.
limit defaults to 50, caps at 200; an explicit limit=0 is a 400, not a full-table scan.time_basis=delivery (default) filters by delivery overlap; time_basis=execution filters by when the action happened.ExportReports permission.Realized and unrealized, broken down.
Voltnir computes profit and loss continuously and streams it on the always-on P&L feed, sliced the way a desk needs to read it.
Realized
Derived from executed trades against a weighted-average cost basis — the booked result of the volume you've actually matched and closed.
Unrealized
Your open net position marked against the live order book, so mark-to-market moves with the market rather than with your last fill.
Every figure is available per contract, per product, and per virtual member — the same attribution that runs through orders, audit and limits runs through P&L, so a brokerage or white-label desk can read each client's result in isolation.
The book, the tape, and the neighbours.
Voltnir applies each M7 delta in roughly fifteen microseconds and maintains the books for you, so every client reads a current book without each one parsing the exchange feed.
Order book
Live depth, L1–L5, per contract — bids and asks with cumulative size, updated as the exchange publishes.
Public trade tape
The market's executed prints as they happen, plus a one-shot history backfill on subscribe, for VWAP and flow context.
Hub-to-hub ATC
The XBID cross-border available-transfer-capacity matrix between delivery areas, on an opt-in stream — the cross-zone picture in one feed.
A terminal you can trade on, on the same API.
Voltnir ships a React trading terminal built on the exact API any client uses — nothing privileged, nothing hidden. The binary can serve it directly on port 8080, or you can deploy it as a static SPA.
Chart & studies
Candlestick chart with order-flow and technical studies: VWAP, volume profile, point of control, cumulative delta, TWAP, opening range, absorption and divergence.
Trade & monitor
L1–L5 depth ladder, the public trade tape, order entry with pre-flight cash / position / throttle checks, live positions, audit, and P&L drill-down.
Admin & ergonomics
Members and users administration, the XBID hub-to-hub matrix, and eight built-in themes (plus one hidden) with adjustable density.
Accounts, licensing and downloads.
The customer portal is where you manage your account and self-serve everything Voltnir delivers outside the binary.
.proto contract for generating your own clients.Licensed, gated, and on your hardware.
Everything that keeps the gateway honest in production — and keeps you in control of it.
See it on SIM.
Voltnir is free on the EPEX SPOT simulator. One static binary, one signed licence file, one outbound connection — minutes to deploy. Bring a Linux server and your EPEX SPOT credentials; we bring everything else.