meridian. tutorial

A hands-on guide to building an autonomous market-making agent for real-world assets with kcolbchain/meridian.

01 Why RWA needs different market making

Traditional AMMs — constant-product (Uniswap v2), concentrated liquidity (v3), or weighted pools (Balancer) — were designed for liquid, fungible tokens that trade continuously across a deep global order book. Real-world assets break every one of those assumptions:

The result: passive LPs in RWA pools lose consistently to anyone with better real-world information. You need an active, oracle-driven agent that controls its own spread and inventory — which is exactly what meridian is.

02 The meridian approach

Meridian is an open-source market-making agent, not a pool contract. It posts and cancels limit orders on any venue (on-chain CLOB, RFQ, or hybrid) using these core principles:

03 Try the simulator

Open meridian.kcolbchain.com (or the local dashboard). In the sidebar:

  1. Set Strategy to adaptive_spread.
  2. Set Asset to Tokenised private credit (USD).
  3. Click Run.

Watch the mid-price chart and the KPI tiles update in real time. Here is what each KPI means:

KPIWhat it tells you
Mid (USD)Current oracle mid-price of the asset.
Effective spreadDistance between the agent's live bid and ask, in basis points. Wider = more conservative.
InventoryNet units the agent currently holds. Positive = long, negative = short.
Realised PnLCash profit from completed round-trips (buys matched by sells).
Inventory PnLMark-to-market value of the open position. This is unrealised.
Fill rateFraction of posted quotes that actually trade. Low fill rate means the spread is too wide for the venue.
Adverse selectionFraction of fills where the mid moves against the agent immediately after. This is the core enemy.
Sharpe-ishRolling mean PnL change over its standard deviation. A quick proxy for risk-adjusted return.

Experiment: crank Toxic flow share to 40%. Watch adverse selection spike and realised PnL decay. This simulates an informed counterparty — the exact scenario where passive AMMs hemorrhage value. Notice how adaptive_spread widens in response, limiting damage compared to constant_spread.

04 Run the Python agent

git clone https://github.com/kcolbchain/meridian.git
cd meridian
pip install -r requirements.txt

python -m src.agents.rwa_market_maker \
  --config config/default.yaml \
  --simulate

Sample output:

[meridian] strategy=adaptive_spread  asset=rwa-credit  mode=simulate
tick  001  mid=100.0000  bid=99.9010  ask=100.0990  inv=0.000   pnl=$0.00
tick  002  mid=100.0340  bid=99.9352  ask=100.1328  inv=0.482   pnl=$0.03
tick  003  mid= 99.9870  bid=99.8695  ask=100.1045  inv=0.482   pnl=-$0.02
...
tick  100  mid=100.1200  bid=100.0014 ask=100.2386  inv=-0.113  pnl=$4.17
[meridian] 100 ticks complete. Sharpe-ish: 0.41  Fill rate: 28%  Adverse: 11%

The --simulate flag runs the agent against a synthetic price path. Drop it and provide venue credentials in config/default.yaml to quote on a live CLOB.

05 Backtest a strategy

python -m src.backtest.engine \
  --strategy adaptive_spread \
  --data data/sample.csv

The backtest engine replays historical price prints through your strategy and reports the same KPIs as the simulator. Key parameters you can set in the config or override via CLI flags:

Output is a JSON report plus optional CSV of the tick-by-tick state, which you can pipe into any plotting tool.

06 Build your own strategy

Every strategy extends BaseStrategy and overrides a single method:

def compute_quotes(self, mid: float, inventory: float, volatility: float)
    -> tuple[float, float, float, float]:
    """Return (bid_price, ask_price, bid_size, ask_size)."""

Here is a minimal "wide spread when scared" strategy in about 20 lines:

# src/strategies/scared_spread.py
from src.strategies.base import BaseStrategy

class ScaredSpread(BaseStrategy):
    """Widens aggressively when vol or inventory is high."""

    name = "scared_spread"

    def __init__(self, config: dict):
        super().__init__(config)
        self.base_bps  = config.get("base_spread_bps", 200)
        self.vol_mult  = config.get("vol_multiplier", 3.0)
        self.inv_limit = config.get("inv_limit", 10.0)
        self.order_size = config.get("order_size", 1.0)

    def compute_quotes(self, mid, inventory, volatility):
        fear = max(1.0, 1.0 + self.vol_mult * (volatility / 100))
        if abs(inventory) > self.inv_limit * 0.5:
            fear *= 2.0                     # double the spread when half-full
        half = (self.base_bps * fear) / 2 / 10_000
        bid_px = mid * (1 - half)
        ask_px = mid * (1 + half)
        return bid_px, ask_px, self.order_size, self.order_size

Register the class in src/strategies/__init__.py, then run it in the backtest: --strategy scared_spread. If the Sharpe-ish holds up, promote it to the simulator dropdown and the live agent.

07 Compliance-gated quoting roadmap

For ERC-3643 security tokens, the agent cannot blindly post quotes to any taker. Before quoting, it must verify that the counterparty holds valid claims on their ONCHAINID identity contract — jurisdiction, accreditation status, and KYC expiry.

The planned compliance_gated strategy wraps any inner strategy and adds a pre-quote check: query the token's identity registry, verify the taker's claims, and suppress the quote if verification fails. This keeps the agent (and its LPs) on the right side of transfer restrictions.

You can explore the identity and compliance contracts we are building at rwa-toolkit.kcolbchain.com. The strategy stub is already visible on the simulator dashboard under the compliance_gated card.

08 Tokenised MM shares roadmap

Issue #18 tracks the design for ERC-4626 vaults per strategy. The idea: depositors fund the agent's inventory by minting vault shares. The agent quotes using the pooled capital, and quoting profits (net of fees and adverse selection) accrue to the vault.

This is how meridian becomes investable — anyone can deposit USDC into, say, the adaptive_spread / rwa-credit vault and earn a share of market-making returns without running infrastructure. The vault's share price reflects cumulative PnL, and withdrawals are gated by an unwind period so the agent is not forced to close positions instantly.

09 Next steps