Skip to content

add MockExchange for offline development and testing#107

Open
mooncitydev wants to merge 3 commits intopmxt-dev:mainfrom
mooncitydev:feat/mock-exchange
Open

add MockExchange for offline development and testing#107
mooncitydev wants to merge 3 commits intopmxt-dev:mainfrom
mooncitydev:feat/mock-exchange

Conversation

@mooncitydev
Copy link
Copy Markdown
Contributor

closes #19

what this does

adds MockExchange — a fully offline, zero-network exchange implementation built on top of @faker-js/faker. it lets developers write and test application code against pmxt without real API keys or an internet connection, which is especially useful in CI/CD pipelines and during rapid prototyping.

how it works

MockExchange extends PredictionMarketExchange and overrides every relevant method with local in-memory implementations. faker is seeded deterministically from each market/outcome id, so the same call always returns the same data across runs — you can write stable assertions against it.

features

  • fetchMarkets / fetchEvents — generates realistic binary and multi-outcome markets grouped into events, with prices, volumes, slugs, tags, and categories
  • fetchOrderBook — produces a realistic CLOB-style bid/ask ladder centred around the mid price
  • fetchOHLCV — generates candlestick data for any resolution (1m through 1d) and any date range
  • fetchTrades — returns a list of fake historical trades for an outcome
  • fetchBalance — returns a configurable starting USDC balance
  • createOrder — records the order in-memory, updates balance and positions, simulates a configurable latency (default 100ms), always fills
  • full order lifecycle: cancelOrder, fetchOrder, fetchOpenOrders, fetchClosedOrders, fetchAllOrders, fetchMyTrades
  • fetchPositions — returns live position state derived from all trades placed so far
  • buildOrder / submitOrder — supported
  • reset() — clears all orders, positions and trades between test cases

usage

import pmxt from 'pmxtjs';

const exchange = new pmxt.Mock({ marketCount: 20, balance: 5000 });

const markets = await exchange.fetchMarkets();
const order = await exchange.createOrder({
  marketId: markets[0].marketId,
  outcomeId: markets[0].yes!.outcomeId,
  side: 'buy',
  type: 'limit',
  price: 0.55,
  amount: 10,
});
console.log(order.status); // 'filled'

const [balance] = await exchange.fetchBalance();
console.log(balance.available); // 4994.5

exchange.reset(); // clean state for next test

made by mooncitydev

implements issue pmxt-dev#19 - a fully offline mock exchange that generates
realistic prediction market data without any network requests.

uses @faker-js/faker with deterministic seeding so every market id
always produces the same prices, titles and order books across runs.
implements fetchMarkets, fetchEvents, fetchOrderBook, fetchOHLCV,
fetchTrades, fetchBalance, fetchPositions, createOrder, cancelOrder,
fetchOrder, fetchOpenOrders, fetchMyTrades, fetchClosedOrders,
fetchAllOrders, buildOrder and submitOrder.
in-memory order store and position tracking update on every createOrder
so balance/positions reflect trades made during the session.
configurable marketCount, starting balance and order latency via
MockExchangeOptions constructor argument.
exports pmxt.Mock alongside all other exchanges in index.ts.
adds reset() helper to wipe session state between test cases.

Made-with: Cursor
@realfishsam
Copy link
Copy Markdown
Contributor

Thanks for this — the concept is solid and it covers the issue requirements well. A few things to address before this is ready to merge:

@faker-js/faker as a production dependency

This is the main concern. faker is ~3.5MB and it would ship to every pmxt-core consumer even if they never use MockExchange. A few options:

  1. Drop faker entirely — a simple seeded PRNG with the existing templates would produce the same deterministic output at zero dependency cost.
  2. Use a dynamic import() so it's only loaded when MockExchange is actually instantiated.
  3. Move MockExchange to a separate optional package.

Option 1 is probably the cleanest here since the data generation doesn't need faker's full feature set.

No tests

610 lines of order lifecycle, balance accounting, and position tracking need test coverage. The position math (entry price averaging, PnL) and balance deductions are especially important to verify.

Orders always fill immediately

createOrder always sets status to filled, which means cancelOrder can never actually work (it rejects anything that isn't open or pending), and fetchOpenOrders always returns empty. Consider adding a way to simulate partial fills or pending orders so consumers can test those flows too.

Mutation

Position tracking and cancelOrder mutate stored objects in place (existing.size = ..., order.status = 'cancelled'). We use immutable patterns in this codebase — create new objects instead of modifying existing ones.

Minor

  • _outcomePrice on InternalOrder is set but never read.
  • Market-type orders get a random price from faker rather than using the order book mid-price.

The foundation here is good — deterministic seeding, proper BaseExchange contract, reset() for test isolation, configurable options. Worth iterating on rather than starting over.

- Replace @faker-js/faker with local SeededRng (mulberry32 + string hash)
- market orders price from same mid as fetchOrderBook (first float)
- limitOrderMode: 'resting' for open/cancel/fill; fillOrder for partial/complete
- Buy resting uses locked USDC; immutable position updates
- Add core/test/unit/mockExchange.core.test.ts

Made-with: Cursor
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Core] Implement MockExchange for offline development

2 participants