Overview
Since the Provider API gives you access to the same markets on different exchanges through a single interface, you can detect price discrepancies and execute hedged trades. This recipe shows a basic cross-provider arbitrage strategy.
Arbitrage carries execution risk. Prices can move between detection and execution. Slippage, fees, and network latency must be factored in. Always start on testnet.
How It Works
Stream prices from both Hyperliquid and Aster for the same market
When the price difference exceeds a threshold (accounting for fees), execute:
Buy on the cheaper exchange
Sell on the more expensive exchange
You now hold a hedged position with locked-in profit
Step 1: Stream Prices from Both Providers
const API_KEY = "ps_live_abc123def456" ;
const SYMBOL = "BTC" ;
const MIN_SPREAD_BPS = 10 ; // Minimum 10bps spread to trade
const prices : Record < string , number > = {};
const ws = new WebSocket (
`wss://api.perps.studio/ws/v1/perps?apiKey= ${ API_KEY } `
);
ws . onopen = () => {
// Subscribe to ticker on both providers
for ( const provider of [ "hyperliquid" , "aster" ]) {
ws . send ( JSON . stringify ({
event: "subscribe" ,
data: {
provider ,
channel: "ticker" ,
params: { symbol: SYMBOL },
},
}));
}
};
ws . onmessage = ( event ) => {
const msg = JSON . parse ( event . data );
if ( msg . channel !== "ticker" ) return ;
prices [ msg . provider ] = parseFloat ( msg . data . markPrice );
// Check for arb opportunity when we have both prices
if ( prices . hyperliquid && prices . aster ) {
checkArbOpportunity ();
}
};
Step 2: Detect Opportunities
function checkArbOpportunity () {
const hlPrice = prices . hyperliquid ;
const asterPrice = prices . aster ;
const spreadBps = Math . abs ( hlPrice - asterPrice ) / Math . min ( hlPrice , asterPrice ) * 10000 ;
if ( spreadBps < MIN_SPREAD_BPS ) return ;
const buyProvider = hlPrice < asterPrice ? "hyperliquid" : "aster" ;
const sellProvider = hlPrice < asterPrice ? "aster" : "hyperliquid" ;
console . log (
`ARB DETECTED: ${ SYMBOL } | ` +
`Buy on ${ buyProvider } @ ${ prices [ buyProvider ] } | ` +
`Sell on ${ sellProvider } @ ${ prices [ sellProvider ] } | ` +
`Spread: ${ spreadBps . toFixed ( 1 ) } bps`
);
executeArb ( buyProvider , sellProvider );
}
Step 3: Execute Hedged Trade
const BASE = "https://api.perps.studio/v1" ;
const ARB_SIZE = "0.01" ; // BTC
async function executeArb ( buyProvider : string , sellProvider : string ) {
// Execute both legs simultaneously
const [ buyResult , sellResult ] = await Promise . allSettled ([
fetch ( ` ${ BASE } /perps/ ${ buyProvider } /orders` , {
method: "POST" ,
headers: {
"X-API-Key" : API_KEY ,
"Content-Type" : "application/json" ,
},
body: JSON . stringify ({
symbol: SYMBOL ,
side: "buy" ,
type: "market" ,
size: ARB_SIZE ,
wallet: WALLET ,
signature: await signOrder ( buyProvider , "buy" ),
nonce: Date . now (),
}),
}). then (( r ) => r . json ()),
fetch ( ` ${ BASE } /perps/ ${ sellProvider } /orders` , {
method: "POST" ,
headers: {
"X-API-Key" : API_KEY ,
"Content-Type" : "application/json" ,
},
body: JSON . stringify ({
symbol: SYMBOL ,
side: "sell" ,
type: "market" ,
size: ARB_SIZE ,
wallet: WALLET ,
signature: await signOrder ( sellProvider , "sell" ),
nonce: Date . now (),
}),
}). then (( r ) => r . json ()),
]);
console . log ( "Buy result:" , buyResult );
console . log ( "Sell result:" , sellResult );
// Check if both legs executed
if ( buyResult . status === "rejected" || sellResult . status === "rejected" ) {
console . error ( "One leg failed! Manual intervention needed." );
// In production: auto-unwind the successful leg
}
}
Step 4: Risk Management
// Track open arb positions
let openArbPositions = 0 ;
const MAX_ARB_POSITIONS = 3 ;
function checkArbOpportunity () {
if ( openArbPositions >= MAX_ARB_POSITIONS ) {
console . log ( "Max arb positions reached, skipping" );
return ;
}
// ... detection logic ...
// Account for fees
const hlFee = 0.00035 ; // taker fee
const asterFee = 0.0005 ;
const totalFeeBps = ( hlFee + asterFee ) * 10000 ;
if ( spreadBps < totalFeeBps + MIN_SPREAD_BPS ) {
return ; // Not profitable after fees
}
executeArb ( buyProvider , sellProvider );
openArbPositions ++ ;
}
Closing Arb Positions
Once the price converges, close both positions:
async function closeArbPosition ( provider : string , side : string ) {
const closeSide = side === "buy" ? "sell" : "buy" ;
await fetch ( ` ${ BASE } /perps/ ${ provider } /orders` , {
method: "POST" ,
headers: {
"X-API-Key" : API_KEY ,
"Content-Type" : "application/json" ,
},
body: JSON . stringify ({
symbol: SYMBOL ,
side: closeSide ,
type: "market" ,
size: ARB_SIZE ,
reduceOnly: true ,
wallet: WALLET ,
signature: await signOrder ( provider , closeSide ),
nonce: Date . now (),
}),
});
}
Key Considerations
Market orders can slip. Use the orderbook depth endpoint to estimate slippage before executing. For large sizes, consider limit orders with tight timeouts.
Funding Rate Differential
If you hold positions across providers for extended periods, funding rate differences will affect your PnL. Monitor funding rates and factor them into your profitability calculations.
If one leg of the arb fails (e.g., rejected order), you have an unhedged position. Implement automatic unwinding of the successful leg if the other fails.
You need margin on both exchanges. Consider using the spot transfer endpoint to move capital between spot and perp wallets as needed.