PM vs HL Edge Scanner
Scans for cross-platform arbitrage edges between Polymarket and Hyperliquid HIP-4 outcome markets. Compares implied probabilities on matching markets and sends a formatted report to your chosen notification channel.
Trigger: Cronjob → Actions: Find Market Edges, If, Hyperliquid Get Outcome Markets, Hyperliquid Get Outcome Price, Pick HL Outcome, Polymarket Search Markets, Polymarket Search Markets, Polymarket Search Markets, Polymarket Search Markets, Notification, Log
PM vs HL Edge Scanner
Created by
B3 Team
1.
Root
2.
Hl Outcomes
3.
Pm Btc
4.
Pm Eth
5.
Pm Sol
About
Scans for cross-platform arbitrage edges between Polymarket and Hyperliquid HIP-4 outcome markets. Compares implied probabilities on matching markets and sends a formatted report to your chosen notification channel.
Preview
Find Market Edges
Action
Code:function transform(input) {
var PROXIMITY = (input.priceProximity || 5) / 100;
var THRESHOLD = input.edgeThreshold || 3;
var hlos = (input.hlOutcomes || []).filter(function(o) { return !o.settled && o.underlying && o.targetPrice; });
var allPm = [].concat(input.pmBtc || [], input.pmEth || [], input.pmSol || [], input.pmCrypto || []);
var seen = {};
var pmMarkets = [];
allPm.forEach(function(m) {
var key = m.conditionId || m.id;
if (!seen[key]) { seen[key] = true; pmMarkets.push(m); }
});
var hlP = input.hlPrice || {};
var hlYesMid = hlP.impliedProbability;
var hlSides = hlP.sides || [];
var hlYes = hlSides.length > 0 ? hlSides[0] : {};
var hlBid = hlYes.bestBid;
var hlAsk = hlYes.bestAsk;
var hlSpreadBps = hlYes.spreadBps;
var hlMeta = input.hlMeta || {};
var assetMap = {
BTC: ['bitcoin', 'btc'], ETH: ['ethereum', 'eth'], SOL: ['solana', 'sol'],
DOGE: ['doge', 'dogecoin'], XRP: ['xrp', 'ripple'], LINK: ['chainlink', 'link'],
AVAX: ['avalanche', 'avax'], SUI: ['sui'], PEPE: ['pepe'], HYPE: ['hype', 'hyperliquid']
};
function extractPrices(text) {
var prices = [];
var m1 = text.match(/\\$(\\d{1,3}(?:,\\d{3})*(?:\\.\\d+)?)/g);
if (m1) m1.forEach(function(p) { prices.push(parseFloat(p.replace(/[$,]/g, ''))); });
var m2 = text.match(/(\\d+(?:\\.\\d+)?)\\s*k\\b/gi);
if (m2) m2.forEach(function(p) { prices.push(parseFloat(p) * 1000); });
return prices;
}
var matches = [];
hlos.forEach(function(hl) {
var underlying = (hl.underlying || '').toUpperCase();
if (!underlying) return;
var target = hl.targetPrice;
if (!target) return;
var keywords = assetMap[underlying] || [underlying.toLowerCase()];
var isMain = (hl.outcomeIndex === hlP.outcomeIndex);
pmMarkets.forEach(function(pm) {
var q = (pm.question || '').toLowerCase();
var hit = keywords.some(function(kw) { return q.indexOf(kw) !== -1; });
if (!hit) return;
var pmPrices = extractPrices(pm.question || '');
if (pmPrices.length === 0) return;
var closestPrice = null;
var closestDiff = Infinity;
pmPrices.forEach(function(p) {
var diff = Math.abs(p - target) / target;
if (diff < closestDiff) { closestDiff = diff; closestPrice = p; }
});
if (closestDiff >= PROXIMITY) return;
var pmYes = parseFloat((pm.outcomePrices || [])[0] || '0');
var diffPct = ((closestPrice - target) / target * 100).toFixed(1);
var edge = null;
var edgeDir = '';
if (isMain && hlYesMid != null) {
edge = ((pmYes - hlYesMid) * 100).toFixed(1);
var ev = parseFloat(edge);
if (ev > THRESHOLD) edgeDir = 'PM overpriced -> sell PM Yes / buy HL Yes';
else if (ev < -THRESHOLD) edgeDir = 'PM underpriced -> buy PM Yes / sell HL Yes';
else edgeDir = 'no significant edge (<' + THRESHOLD + 'pp)';
}
matches.push({
underlying: underlying, hlTarget: target, hlExpiry: hl.expiry || '',
hlOutcomeIndex: hl.outcomeIndex,
hlYesMid: isMain ? hlYesMid : null,
hlBid: isMain ? hlBid : null, hlAsk: isMain ? hlAsk : null,
hlSpreadBps: isMain ? hlSpreadBps : null,
pmQuestion: pm.question, pmSlug: pm.slug || '',
pmPrice: closestPrice, pmYes: pmYes,
pmImplied: (pmYes * 100).toFixed(1),
pmVolume: pm.volume || 0, pmLiquidity: pm.liquidity || 0,
priceDiff: diffPct, edge: edge, edgeDir: edgeDir
});
});
});
matches.sort(function(a, b) {
var ea = a.edge != null ? Math.abs(parseFloat(a.edge)) : 0;
var eb = b.edge != null ? Math.abs(parseFloat(b.edge)) : 0;
if (eb !== ea) return eb - ea;
return Math.abs(parseFloat(a.priceDiff)) - Math.abs(parseFloat(b.priceDiff));
});
var now = new Date().toISOString().slice(0, 16).replace('T', ' ');
var s = '*PM vs HL Edge Scanner* -- ' + now + ' UTC\n\n';
if (hlYesMid != null) {
s += '*HL:* ' + (hlMeta.underlying || '?') + ' > $' + (hlMeta.targetPrice || 0).toLocaleString();
s += ' | Yes mid: *' + (hlYesMid * 100).toFixed(1) + '%*';
s += ' | bid/ask: ' + (hlBid != null ? hlBid.toFixed(3) : '?') + '/' + (hlAsk != null ? hlAsk.toFixed(3) : '?');
if (hlSpreadBps != null) s += ' (' + hlSpreadBps.toFixed(0) + 'bps)';
s += ' | exp ' + (hlMeta.expiry || '?') + '\n\n';
}
s += hlos.length + ' HL outcomes | ' + pmMarkets.length + ' PM markets | *' + matches.length + ' overlaps*\n\n';
if (matches.length > 0) {
matches.slice(0, 10).forEach(function(m, i) {
s += '*' + (i + 1) + '.* ' + (m.pmQuestion || '').slice(0, 85) + '\n';
s += ' PM $' + (m.pmPrice || 0).toLocaleString() + ' vs HL $' + (m.hlTarget || 0).toLocaleString() + ' (target diff ' + m.priceDiff + '%)\n';
s += ' PM Yes: *' + m.pmImplied + '%*';
if (m.hlYesMid != null) {
s += ' | HL Yes: *' + (m.hlYesMid * 100).toFixed(1) + '%*';
s += ' | Edge: *' + m.edge + 'pp*';
}
s += '\n';
if (m.edgeDir) s += ' -> _' + m.edgeDir + '_\n';
s += ' Vol $' + (m.pmVolume / 1000).toFixed(0) + 'k | Liq $' + (m.pmLiquidity / 1000).toFixed(0) + 'k\n\n';
});
} else {
s += '_No overlapping markets found._\n';
}
return { report: s, edgeCount: matches.length, matches: matches, pmTotal: pmMarkets.length, hlTotal: hlos.length };
}
Input:{"edgeThreshold":"{{$props.edgeThreshold}}","hlMeta":"{{pick_hl.result.output}}","hlOutcomes":"{{hl_outcomes.result.outcomes}}","hlPrice":"{{hl_price.result}}","pmBtc":"{{pm_btc.result.markets}}","pmCrypto":"{{pm_crypto.result.markets}}","pmEth":"{{pm_eth.result.markets}}","pmSol":"{{pm_sol.result.markets}}","priceProximity":"{{$props.priceProximity}}"}
has_edges
Action
Condition:{"$and":[{"{{find_edges.result.output.edgeCount}}":{"$gt":0}}]}
hl_outcomes
Action
hl_price
Action
Orderbook Depth:5
Outcome Index:Pick HL Outcome:output.outcomeIndex
Pick HL Outcome
Action
Code:function transform(input) {
var valid = (input.outcomes || []).filter(function(o) {
return !o.settled && o.underlying && o.targetPrice;
});
if (valid.length === 0) return { outcomeIndex: 0, count: 0, underlying: '', targetPrice: 0, expiry: '' };
var o = valid[0];
return {
outcomeIndex: o.outcomeIndex,
underlying: o.underlying,
targetPrice: o.targetPrice,
expiry: o.expiry || '',
count: valid.length
};
}
Input:{"outcomes":"{{hl_outcomes.result.outcomes}}"}
pm_btc
Action
Limit:30
Order By:volume
Query:bitcoin
pm_crypto
Action
Limit:30
Order By:volume
Query:crypto price
pm_eth
Action
Limit:30
Order By:volume
Query:ethereum
pm_sol
Action
Limit:20
Order By:volume
Query:solana
root
Action
Schedule:
DTSTART:20260101T000000Z
RRULE:FREQ=HOURLY;INTERVAL={{$props.scanInterval}}
send_notify
Message configured
{{find_edges.result.output.report}}
skip
Action
Edge scan: find_edges.result.output.hlTotal HL outcomes, find_edges.result.output.pmTotal PM markets, 0 overlaps
Press enter or space to select a node. You can then use the arrow keys to move the node around. Press delete to remove it and escape to cancel.
Press enter or space to select an edge. You can then press delete to remove it or escape to cancel.
Loading chat...