尚未偵測到更新。GitHub 發布版本後會顯示在這裡。
個人 GitHub 帳號下公開儲存庫的擁有者可以登入並驗證所有權,自行管理介紹,無需管理員核准。
登入並產生驗證檔案,在 30 分鐘內將其提交至此儲存庫的預設分支。其他分支或分叉儲存庫中的檔案無效。然後在下方檢查檔案,即可無需管理員核准直接管理專案。請保留檔案以維持管理權限。
如需登入,將前往登入頁面。登入後請回到此處,再次點擊所選的驗證按鈕。
在專案資料夾中執行 /nomorevibe verify,即可驗證所有權並自行管理介紹。
一分鐘完成驗證正在載入完整 README。 以下為已儲存的摘錄。
Hedge Lab — delta-neutral funding arbitrage across 7 perpetual venues
A research and execution-support tool for delta-neutral funding arbitrage. It normalises live
funding rates from seven perpetual venues, finds pairs where the same asset pays materially
different funding on two of them, and sizes the position against the constraint that actually
binds.
Live demo: — the dashboard is open to everyone; the
scanner, ranking, position and journal screens ask for a Telegram sign-in.檢視完整 README ↗A research and execution-support tool for delta-neutral funding arbitrage. It normalises live funding rates from seven perpetual venues, finds pairs where the same asset pays materially different funding on two of them, and sizes the position against the constraint that actually binds.
Live demo: https://hedge-lab-demo.vercel.app — the dashboard is open to everyone; the scanner, ranking, position and journal screens ask for a Telegram sign-in.
A perpetual future pays funding — a periodic transfer between longs and shorts that keeps the contract near spot. Venues arrive at different rates for the same asset. Long one, short the other, and price movement cancels: the position is delta-neutral and the funding difference is the return.
Finding those pairs is easy. Deciding which are actually tradeable is not.
| Venue | Funding interval | Rate published as | Leverage cap derived from |
|---|---|---|---|
| Variational | 4h / 8h per asset | annualised, RFQ-quoted | not published (RFQ model) |
| Ethereal | 1h | hourly | maxLeverage |
| Nado | 1h | x18 fixed-point | long_weight_initial_x18 → 1/(1−w) |
| Lighter | 1h settlement | 8h-equivalent rate | min_initial_margin_fraction → 10000/f |
| Lighter (Robinhood Chain) | 1h | same | same |
| Aster | 1h / 2h / 4h / 8h per symbol | per-interval | requiredMarginPercent → 100/pct |
| edgeX | 4h | per-interval | riskTierList[0].maxLeverage |
Get any of these wrong and you get a plausible-looking number that is off by 8×. Aster alone
publishes four different intervals across its symbols, so the interval must be read per symbol
from /fundingInfo or every APR on that venue is wrong. Verifying Lighter's convention against
Hyperliquid's hourly rate gave a ratio of exactly 8.000 — the published rate is 8h-equivalent
even though settlement is hourly.
An eighth venue, Phoenix (Solana), is wired in as a Variational cross only — it is not in
the combined matrix. It ticks hourly, fundingRate × 87.6 gives the annual percentage
(calibrated against Hyperliquid), and its public API has no 24h volume, so that column shows
open interest instead.
Raw spread is a poor signal alone. Three derived columns encode what the measurements showed:
Rés (gap) — the price difference between venues, not the funding difference. A
delta-neutral round's basis P&L is exactly the change in this number while the position is open.
On thin books it moves further in an hour than the funding pays in a day, which makes it the
binding constraint rather than the spread.
Egyirány (aligned) — whether the gap and the funding pull the same way: does the funding
want you to short the more expensive leg? When aligned, a narrowing gap pays on top of the
carry. When opposed, they fight. Two live rounds on consecutive days, same method, differing
only in this flag: +$12.73 and −$4.61.
Ítélet (verdict) — reads each venue's own measured history rather than the current tick:
| meaning | |
|---|---|
⇄ | reversed — the 7-day average points the opposite way. One asset read −703.9% average against +1914.3% current, and turned back within hours |
⚠ | spike — far above its own average; it will revert |
✓ | structural — 72h+ in one direction, safe to size against the average |
~ | young — a real direction, under three days old |
Funding series span orders of magnitude — one asset ran between 11% and −17520% with most readings sitting at 11%. On a linear axis one spike sets the range and everything else collapses onto the zero line. The vertical axis is symlog: linear near zero, logarithmic further out, negatives handled, unit set per series from its own median absolute value.
Six screens, plus a deep dive that opens from any row:
Sign-in is optional for whoever deploys it, and the app behaves differently in each case:
| Dashboard, deep dive | Pairs Scanner, Best Pairs, Gap Watch, Position, History | Where state lives | |
|---|---|---|---|
| No Telegram bot configured | open | open | server memory, one shared session |
| Bot configured, visitor not signed in | open | behind a sign-in card | — |
| Signed in | open | open | Supabase, per Telegram account |
A signed-in user's configuration, open round (entry price, entry gap, opening time, funding accrued so far) and journal are stored per account, so a round follows them between devices and survives a restart.
The gate is in the page, not in the API: the market-data endpoints below stay public. Journal writes are the exception — the server refuses them without a session.
The repository ships 99 hourly snapshots taken 16–20 August 2026 across ~700 pairs. Each snapshot holds funding rate and price per venue per symbol. Nothing else is recorded.
While it runs, the server adds at most one snapshot per hour, taken when a scan runs, and
keeps 30 days. With Supabase configured each snapshot is also written to a shared
lab_snapshot table and read back on boot.
Two consequences:
GET / → landing page (overview, conventions, signals)
GET /app → the app
GET /api/scan → full computed scanner payload (cached 60 s)
GET /api/dive/<symbol> → per-asset detail, series, book depth (?leg=<usd>)
GET /api/series/<symbol> → that asset's series from the stored history only
GET /api/archive → days that have snapshots
GET /api/archive/<day> → that day's hourly snapshots
GET /api/me → whether sign-in is enabled and who is signed in
npm start # http://localhost:8879
No API keys, no database, no build step, no dependencies. Node 18+. Without any environment
variables there is no sign-in, nothing is gated, and everything — settings, open round,
journal — lives in memory and resets on restart. PORT changes the port.
One service, one domain. The same Node process serves the landing page (/), the app
(/app) and the API, so there is no second origin, no CORS and no proxy layer.
Vercel serverless landing + app + API
Supabase free project users, journal, settings, history, login (recommended on Vercel)
Telegram login only one "signed in" reply per login (optional, needs Supabase)
server.js runs two ways from the same code: locally it starts a real, always-on
http.createServer (node server.js); on Vercel, api/index.js re-exports the same
request handler as a serverless function, and vercel.json rewrites every path — /,
/app, /api/* — to it, so the whole app stays on one origin without a second service or a
proxy layer. The rewrite passes the original path along as ?__p=…, because Vercel hands
the function the rewrite target rather than the path the visitor asked for.
The tradeoff of serverless is state. A serverless function has no persistent memory between requests — a different one (or a fresh cold start) can serve the next call. Concretely, on Vercel:
/api/scan request may land on
a cold instance and re-run the full venue fetch (~5s) instead of hitting a warm
60-second cache. Slower under load, never broken.None of this needs any setup to deploy — vercel deploy or connecting the repo on
vercel.com works with zero configuration. Every environment
variable below is optional and the server degrades cleanly without them, which is also
what makes git clone && npm start work locally with zero setup.
| Variable | Effect when unset |
|---|---|
SUPA_URL, SUPA_KEY | everything falls back to memory (unreliable on Vercel, fine locally). SUPA_KEY is the service key; it stays on the server |
TG_BOT_TOKEN, TG_BOT_NAME | no sign-in and no gate; the app is fully usable anonymously |
SESSION_SECRET | random per boot — on Vercel, set this explicitly, or every cold start invalidates existing sessions |
Set the Telegram variables only together with the Supabase ones. The sign-in flow stores its one-time codes in Supabase, so a bot without a database switches the gate on with no way through it.
For persistence, run schema.sql once in the Supabase SQL editor. Row-level
security is on with no policies, so only the server's service key can reach the tables.
render.yaml is also in the repo: a persistent Node server has none of the tradeoffs
above, at the cost of a real server to run. It works standalone if you'd rather use it.
Sign-in goes through the bot's deep link. Telegram's Login Widget stopped working in
September 2026 — its oauth.telegram.org/auth popup now answers deprecated — so the
button it drew led nowhere.
t.me/<bot>?start=<code> link./start <code> to the bot's webhook, which records who pressed
it and replies with a one-line confirmation.Setup:
schema.sql in Supabase (it creates the login_nonce table).TG_BOT_TOKEN, TG_BOT_NAME, SESSION_SECRET and the Supabase variables on Vercel.https://<your-domain>/api/tg/setup once. It registers
https://<your-domain>/api/tg/webhook with Telegram and prints Telegram's answer.What keeps it honest:
The old widget callback (/api/auth/telegram, HMAC-SHA256 over the sorted fields keyed by
SHA256(bot_token)) is still in the code but nothing calls it any more.
This is a public demo built from a private system that trades real capital. Nothing here can place an order: there are no private keys, no signing and no exchange credentials anywhere in the code. The optional wallet field takes a public address and only reads that address's open positions on Nado and Lighter, to compare them with the round you are tracking. The private system's own trade history is not part of this repository, and every journal starts empty.
Advisory only. Not financial advice.