mmflow API · Cookbook
Cookbook
End-to-end recipes — every snippet is a complete, runnable mini-project. Drop it next to the SDK file, set one env var, and it runs. TypeScript + Python side-by-side.
Whale alert Discord bot
Discord webhook + SSE stream
Post to a Discord channel every time someone takes a $250k+ position on Hyperliquid.
Setup
- Create a Discord webhook in your channel settings (Edit Channel → Integrations → Webhooks).
- Set DISCORD_WEBHOOK environment variable to the webhook URL.
- Run the script. Reconnects automatically on disconnect.
// whale-bot.ts — Bun / Deno / Node 18+
import { MmflowClient } from "./mmflow";
const WEBHOOK = process.env.DISCORD_WEBHOOK!;
const MIN_USD = 250_000;
const mm = new MmflowClient();
async function post(content: string) {
await fetch(WEBHOOK, {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ content }),
});
}
while (true) {
const { events } = mm.streamWhales({ minUsd: MIN_USD });
try {
for await (const ev of events) {
if (ev.type !== "whale") continue;
const w = ev.data;
const arrow = w.side === "buy" ? "📈" : "📉";
const verb = w.side === "buy" ? "long" : "short";
const usd = (w.notionalUsd / 1e6).toFixed(2) + "M";
await post(`${arrow} ${w.coin} ${verb} $${usd} @ $${w.price} — ${w.venue}`);
}
} catch (err) {
console.error("stream ended, reconnecting in 3s", err);
await new Promise((r) => setTimeout(r, 3000));
}
}Expected output
📈 BTC long $1.28M @ $64,320 — hyperliquid 📉 HYPE short $420k @ $28.41 — hyperliquid 📈 SOL long $310k @ $145.20 — hyperliquid
Deploy
PM2 / systemd / Docker — the script is a long-running process. Memory footprint stays under 50 MB.
Daily CSV snapshot
Read-only one-shot · cron-friendly
Once a day, dump the current OI + funding + 24h liquidations table to a CSV file. Drop into cron.
Setup
- Save as snapshot.{ts,py} next to mmflow.{ts,py}.
- Run by hand once to verify output, then add to cron (e.g. 0 0 * * * /path/to/snapshot.py).
- Files land at ./snapshots/YYYY-MM-DD.csv.
// snapshot.ts
import { writeFile, mkdir } from "node:fs/promises";
import { MmflowClient } from "./mmflow";
const mm = new MmflowClient();
const today = new Date().toISOString().slice(0, 10);
await mkdir("snapshots", { recursive: true });
const [oi, funding, liqs] = await Promise.all([
mm.perpsOi(),
mm.perpsFunding(),
mm.perpsLiquidations(),
]);
const fundBy = new Map(funding.data.map((r) => [r.coin, r.rate]));
const liqBy = new Map(liqs.data.map((r) => [r.coin, r]));
const rows = [
"coin,oi_usd,funding_8h_bps,liq24h_long_usd,liq24h_short_usd",
...oi.data.map((r) => {
const f = fundBy.get(r.coin) ?? 0;
const l = liqBy.get(r.coin);
// Convert 1h rate → 8h bps for human-readable column.
const fund8hBps = (f * 8 * 1e4).toFixed(2);
return [
r.coin,
r.oiUsd.toFixed(2),
fund8hBps,
(l?.long24hUsd ?? 0).toFixed(0),
(l?.short24hUsd ?? 0).toFixed(0),
].join(",");
}),
].join("\n");
await writeFile(`snapshots/${today}.csv`, rows);
console.log(`wrote snapshots/${today}.csv with ${oi.data.length} rows`);Expected output
coin,oi_usd,funding_8h_bps,liq24h_long_usd,liq24h_short_usd BTC,1284390210.50,0.83,18402100,9281400 ETH,418394100.20,1.21,9120400,4502100 SOL,142028104.10,2.04,3812400,1284900 …
Deploy
cron, GitHub Actions schedule, Vercel Cron, Cloudflare Workers Cron — any scheduler with HTTPS egress.
Funding-arb scanner
Cross-venue compare · terminal one-liner
Find the largest HL vs Binance funding spread right now. Print top 5 sorted by annualized arb APR.
Setup
- Drop alongside the SDK file.
- Run on demand — no scheduler needed.
- Calls /perps/funding with venues=hl,binance,bybit so the scanner has real cross-venue rates to compute spreads.
// arb-scan.ts
import { MmflowClient } from "./mmflow";
const mm = new MmflowClient();
const { data } = await mm.perpsFunding({
venues: ["hl", "binance", "bybit"],
});
// Group rows by coin → venue → annualized APR.
const apr = (rate: number, hours: number) =>
rate * (24 / hours) * 365 * 100;
const byCoin = new Map<string, Record<string, number>>();
for (const r of data) {
const cur = byCoin.get(r.coin) ?? {};
cur[r.venue] = apr(r.rate, r.periodHours);
byCoin.set(r.coin, cur);
}
// Compute spread = HL minus best CEX leg per coin.
const spreads: Array<{ coin: string; hl: number; cexVenue: string; cex: number; spreadPp: number }> = [];
for (const [coin, venues] of byCoin) {
const hl = venues["hyperliquid"];
if (hl == null) continue;
const cexEntries = (["binance", "bybit"] as const)
.map((v) => ({ v, apr: venues[v] }))
.filter((x) => x.apr != null);
if (cexEntries.length === 0) continue;
// Pick CEX leg with the OPPOSITE sign to HL — that's the arb.
const oppSign = cexEntries.find((x) => Math.sign(x.apr) !== Math.sign(hl))
?? cexEntries.reduce((a, b) => (Math.abs(a.apr) < Math.abs(b.apr) ? a : b));
spreads.push({ coin, hl, cexVenue: oppSign.v, cex: oppSign.apr, spreadPp: hl - oppSign.apr });
}
spreads.sort((a, b) => Math.abs(b.spreadPp) - Math.abs(a.spreadPp));
console.log("Top HL-vs-CEX funding spreads (annualized):");
for (const s of spreads.slice(0, 5)) {
const sign = (n: number) => (n >= 0 ? "+" : "");
console.log(
` ${s.coin.padEnd(6)} HL ${sign(s.hl)}${s.hl.toFixed(1)}% APR ${s.cexVenue.padEnd(7)} ${sign(s.cex)}${s.cex.toFixed(1)}% spread ${sign(s.spreadPp)}${s.spreadPp.toFixed(1)}pp`,
);
}Expected output
Top HL-vs-CEX funding spreads (annualized): ETH HL +49.3% APR binance +18.2% spread +31.1pp SOL HL +42.1% APR bybit +12.4% spread +29.7pp ARB HL +38.9% APR binance +14.8% spread +24.1pp AVAX HL +33.1% APR binance +11.6% spread +21.5pp XRP HL +28.4% APR bybit +8.9% spread +19.5pp
Deploy
One-shot terminal command — no background process.
Gamma-flip break alert
SSE + REST blend · vol-regime alerts
Watch BTC funding stream + GEX. Alert when BTC spot crosses the gamma flip strike (vol regime change).
Setup
- Set TELEGRAM_TOKEN + TELEGRAM_CHAT_ID for notifications, or replace post() with any alerting transport.
- Run as a long-lived process. Re-fetches GEX every 5 minutes; reads trades stream continuously.
// gamma-flip.ts
import { MmflowClient } from "./mmflow";
const mm = new MmflowClient();
const TG = process.env.TELEGRAM_TOKEN!;
const CHAT = process.env.TELEGRAM_CHAT_ID!;
async function notify(msg: string) {
console.log("→ posted:", msg);
if (!TG) return;
await fetch(`https://api.telegram.org/bot${TG}/sendMessage`, {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ chat_id: CHAT, text: msg }),
});
}
let flip: number | null = null;
let lastPrice: number | null = null;
async function refreshGex() {
const { data } = await mm.optionsGex({ underlying: "BTC" });
flip = data.gammaFlip;
console.log(`[gex refreshed] BTC flip = $${flip?.toFixed(0) ?? "n/a"}`);
}
await refreshGex();
setInterval(refreshGex, 300_000);
const { events } = mm.streamTrades({ coin: "BTC" });
for await (const ev of events) {
if (ev.type !== "trade" || flip == null) continue;
const p = ev.data.price;
if (lastPrice != null && lastPrice < flip && p >= flip) {
await notify(`🟢 BTC crossed UP through gamma flip @ $${p.toFixed(0)} (negative-γ → positive-γ regime)`);
} else if (lastPrice != null && lastPrice > flip && p <= flip) {
await notify(`🔴 BTC crossed DOWN through gamma flip @ $${p.toFixed(0)} (positive-γ → negative-γ regime)`);
}
lastPrice = p;
}Expected output
[gex refreshed] BTC flip = $63,800
[trade] BTC buy @ $63,810 → crossing UP through flip!
→ posted: 🟢 BTC crossed UP through gamma flip @ $63,810
(negative-γ → positive-γ regime; vol suppression ahead)Deploy
PM2 / systemd / Docker. ~50 MB memory.
Telegram funding-spike alert
Telegram Bot API + funding polling
Fire a Telegram message whenever any tracked HL coin's funding rate crosses ±5bp.
Setup
- Create a Telegram bot via @BotFather → get the bot token.
- Find your chat ID (or group ID) — easiest via @userinfobot.
- Set TG_BOT_TOKEN and TG_CHAT_ID env vars. Optionally pass a COINS list (comma-separated).
// funding-alert.ts — Bun / Deno / Node 18+
import { MmflowClient } from "./mmflow";
const TOKEN = process.env.TG_BOT_TOKEN!;
const CHAT = process.env.TG_CHAT_ID!;
const COINS = (process.env.COINS ?? "BTC,ETH,SOL,HYPE").split(",");
const THRESHOLD_BPS = 5;
const mm = new MmflowClient();
const lastFlag: Record<string, string | null> = {};
async function tg(msg: string) {
await fetch(`https://api.telegram.org/bot${TOKEN}/sendMessage`, {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ chat_id: CHAT, text: msg }),
});
}
setInterval(async () => {
for (const coin of COINS) {
const f = await mm.perpsFunding({ coin });
const bps = f.venues.hyperliquid.rate * 10_000;
const flag = bps > THRESHOLD_BPS ? "long" : bps < -THRESHOLD_BPS ? "short" : null;
if (flag && flag !== lastFlag[coin]) {
const verb = flag === "long" ? "long-pay" : "short-pay";
await tg(`⚡ ${coin} funding spiked: ${verb} ${bps.toFixed(1)}bp/h (HL)`);
}
lastFlag[coin] = flag;
}
}, 60_000);Expected output
⚡ BTC funding spiked: long-pay 7.2bp/h (HL) ⚡ ETH funding flipped: short-pay -6.1bp/h (HL)
Deploy
Cron-able. The script polls every 60s and only fires on transitions across the ±5bp threshold so you don't get spammed every minute the rate stays elevated.
Jupyter OI-regime classifier
Notebook research — daily run
Pull /perps/oi/history, fit a 180-day rolling z-score, and label the current regime as compressed / extended / stretched.
Setup
- Drop the snippet into a fresh Jupyter notebook.
- Install numpy and the mmflow SDK (`pip install mmflow-sdk numpy`).
- Run the cell. The output is a one-line regime label for each coin in COINS.
// regime.ts — same logic, TS variant
import { MmflowClient } from "./mmflow";
const mm = new MmflowClient();
const coins = ["BTC", "ETH", "SOL", "HYPE"];
for (const coin of coins) {
const { points } = await mm.perpsOiHistory({ coin, days: 180 });
const series = points.map((p) => p.oi);
const mean = series.reduce((s, x) => s + x, 0) / series.length;
const sd = Math.sqrt(series.reduce((s, x) => s + (x - mean) ** 2, 0) / series.length);
const z = (series[series.length - 1] - mean) / sd;
const regime = Math.abs(z) < 0.5 ? "compressed" : z > 1.5 ? "stretched" : "extended";
console.log(`${coin.padEnd(4)} z=${z.toFixed(2).padStart(6)} → ${regime}`);
}Expected output
BTC z= 1.82 → stretched ETH z= 0.31 → compressed SOL z=-1.15 → extended HYPE z= 2.41 → stretched
Deploy
Schedule the notebook via Papermill + cron for a daily regime feed. Pipe the output to your Discord / Notion / spreadsheet.
Vol-surface fitter
Quant research — daily cron
Pull /options/skew daily, fit a cubic spline through the delta-bucketed IVs, persist a daily snapshot.
Setup
- pip install mmflow-sdk numpy scipy
- Set DATA_DIR to the directory where snapshots should land (one CSV per day).
- Run via cron at 00:05 UTC for clean day boundaries.
// vol-surface.ts — TS variant (uses simple linear interp; for spline,
// use a numeric library like ml-spline).
import { writeFileSync, mkdirSync } from "fs";
import { MmflowClient } from "./mmflow";
const mm = new MmflowClient();
const dir = process.env.DATA_DIR ?? "data";
mkdirSync(dir, { recursive: true });
for (const ccy of ["BTC", "ETH"]) {
const skew = await mm.optionsSkew({ ccy });
const points = skew.points
.map((p) => `${p.delta},${p.dte},${p.iv.toFixed(4)}`)
.join("\n");
const day = new Date().toISOString().slice(0, 10);
writeFileSync(`${dir}/${day}-${ccy.toLowerCase()}-vol.csv`, `delta,dte,iv\n${points}`);
console.log(`${ccy} vol surface snapshot saved → ${dir}/${day}-${ccy.toLowerCase()}-vol.csv`);
}Expected output
BTC vol surface snapshot saved → data/2026-05-18-btc-vol.csv ETH vol surface snapshot saved → data/2026-05-18-eth-vol.csv
Deploy
Cron daily. The CSV files build a vol-surface time-series you can backtest against — fit per-day, then compare today's surface vs the 30-day median for cheap/rich identification.
Embeddable HL ticker widget
Embed in your community Discord forum or docs site
A self-contained HTML snippet a forum admin can drop into Discord / Discourse / a docs page — shows live HL whale prints.
Setup
- Paste the iframe snippet anywhere that supports HTML — Discourse forum post, Notion embed, your own site.
- Adjust ?minUsd= for the threshold. The widget auto-reconnects on disconnect.
<!-- Anywhere HTML is allowed: forum post, Notion embed, your site --> <iframe src="https://mmflow.ai/embed/whale-ticker?minUsd=100000" width="100%" height="48" frameborder="0" loading="lazy" title="HL whale ticker" ></iframe> <!-- Filter to a specific coin --> <iframe src="https://mmflow.ai/embed/whale-ticker?minUsd=50000&coin=HYPE" width="100%" height="48" frameborder="0" loading="lazy" ></iframe>
Expected output
A 48px-tall continuously-scrolling ticker showing every HL print above your threshold.
Deploy
No deployment needed. The widget hits mmflow.ai/embed/whale-ticker directly — we serve the chrome, you embed the iframe.
VS Code status-bar funding extension
Local IDE integration — for traders writing code
Show the current HL funding rate for a watched coin in your editor's status bar — a ~300-line VS Code extension stub.
Setup
- Scaffold: `yo code` → New TypeScript Extension.
- Drop the snippet into `src/extension.ts` and `package.json` configuration.
- F5 to launch in an Extension Development Host.
// src/extension.ts
import * as vscode from "vscode";
const COIN = vscode.workspace.getConfiguration("mmflow").get<string>("coin", "BTC");
let status: vscode.StatusBarItem;
export function activate(ctx: vscode.ExtensionContext) {
status = vscode.window.createStatusBarItem(vscode.StatusBarAlignment.Right, 100);
status.command = "mmflow.openLive";
status.show();
ctx.subscriptions.push(status);
ctx.subscriptions.push(
vscode.commands.registerCommand("mmflow.openLive", () =>
vscode.env.openExternal(vscode.Uri.parse("https://mmflow.ai/live"))
),
);
refresh();
const interval = setInterval(refresh, 60_000);
ctx.subscriptions.push({ dispose: () => clearInterval(interval) });
}
async function refresh() {
try {
const r = await fetch(`https://mmflow.ai/api/v1/perps/funding?coin=${COIN}`);
const f = await r.json();
const bps = f.venues.hyperliquid.rate * 10_000;
const sign = bps >= 0 ? "+" : "";
status.text = `${COIN} fnd ${sign}${bps.toFixed(1)}bp`;
status.color = bps > 5 ? "#34d399" : bps < -5 ? "#ef4444" : undefined;
} catch {
status.text = `${COIN} fnd —`;
}
}Expected output
Status bar shows: `BTC fnd +4.2bp` — refreshes every 60s, color-codes green for long-pay and pink for short-pay.
Deploy
Package with `vsce package` and publish to the marketplace. Or `code --install-extension <vsix>` for personal use.
MetaMask Snap — gamma-flip wallet alert
On-chain wallet UX
Surface a gamma-flip break notification inside MetaMask. Reads the user's BTC balance and pings when price crosses the dealer gamma flip level.
Setup
- Init a Snap project: `yarn create @metamask/snap`.
- Replace `src/index.ts` with the snippet below and `snap.manifest.json` permissions.
- Test via the MetaMask Flask developer build.
// src/index.ts — MetaMask Snap stub
import type { OnCronjobHandler } from "@metamask/snaps-sdk";
const COIN = "BTC";
let lastPrice: number | null = null;
let lastFlip: number | null = null;
export const onCronjob: OnCronjobHandler = async ({ request }) => {
if (request.method !== "tick") return;
// 1. Pull current mid + gamma flip from mmflow.
const [midRes, gexRes] = await Promise.all([
fetch(`https://mmflow.ai/api/v1/perps/mids?coin=${COIN}`),
fetch(`https://mmflow.ai/api/v1/options/gex?ccy=${COIN}`),
]);
const mid = (await midRes.json()).price;
const gex = await gexRes.json();
const flip: number | null = gex.gammaFlip ?? null;
// 2. Detect a crossing vs the previous tick.
if (lastPrice != null && flip != null) {
if (lastPrice < flip && mid >= flip) {
await notify(`BTC crossed UP through gamma flip @ $${mid.toFixed(0)}`);
} else if (lastPrice > flip && mid <= flip) {
await notify(`BTC crossed DOWN through gamma flip @ $${mid.toFixed(0)}`);
}
}
lastPrice = mid;
lastFlip = flip;
};
async function notify(message: string) {
await snap.request({
method: "snap_notify",
params: { type: "native", message },
});
}Expected output
MetaMask drops a notification: "BTC crossed UP through gamma flip @ $67,140 — dealer hedging regime change. Open mmflow /live for context."
Deploy
Publish the Snap to npm under the @your-handle/mmflow-snap name. Users add it via the Snaps directory. Anyone holding BTC sees a wallet-level notification on each gamma flip break.
Got a recipe to share?
Open a PR with your recipe and we'll feature it here. Cookbook entries are MIT-licensed and credited to the author. REST reference