This guide walks you through deploying an automated SOL arbitrage bot between Hyperliquid and Derive using hummingbot-api. Two strategies are covered: spot-spot arbitrage (simpler, lower risk) and funding rate arbitrage (perpetuals, earns funding payments). Both strategies exploit price and rate discrepancies between the two exchanges with simultaneous opposing positions.
- Architecture Overview
- Prerequisites
- Configure Exchange Credentials
- Choose a Strategy
- Create the Controller Config
- Deploy the Bot
- Monitor & Manage
- Stop & Archive
- Parameter Reference
- Tips & Common Issues
┌─────────────────────────────────────────────────────────────────┐
│ hummingbot-api │
│ │
│ POST /controllers/configs/ ──► YAML config stored │
│ POST /bot-orchestration/deploy-v2-controllers │
│ │ │
│ ▼ │
│ Docker container spawned with v2_with_controllers.py │
│ │ │
│ ▼ │
│ ArbitrageController (generic) │
│ ├── ArbitrageExecutor (buy on A, sell on B) │
│ └── ArbitrageExecutor (buy on B, sell on A) │
└─────────────────────────────────────────────────────────────────┘
│ │
▼ ▼
┌───────────────┐ ┌─────────────────┐
│ Hyperliquid │ │ Derive │
│ SOL-USD │ │ SOL-USDC (spot) │
│ SOL-USD perp │ │ SOL-USD (perp) │
└───────────────┘ └─────────────────┘
How arbitrage works:
sequenceDiagram
participant C as ArbitrageController
participant E1 as ArbitrageExecutor (A→B)
participant HL as Hyperliquid
participant DV as Derive
C->>C: Compute mid-prices on both exchanges
C->>C: spread = (sell_price - buy_price) / buy_price - fees
alt spread > min_profitability
C->>E1: CreateExecutorAction
E1->>HL: Market BUY SOL-USD
E1->>DV: Market SELL SOL-USDC
E1->>C: Report PnL
else
C->>C: Wait delay_between_executors seconds
end
The controller simultaneously watches both directions (buy HL / sell Derive, and buy Derive / sell HL) and fires when the spread exceeds min_profitability after fees.
| Requirement | Details |
|---|---|
| hummingbot-api running | https://api.metallorum.duckdns.org/ |
| Hyperliquid account | Wallet address + private key |
| Derive account | Wallet address + private key |
| Funded wallets | SOL + USD on Hyperliquid; SOL + USDC on Derive |
master_account credentials |
API account already set up in hummingbot-api |
Verify the API is reachable and your account exists:
# Check available accounts
curl -u admin:YOUR_PASSWORD \
https://api.metallorum.duckdns.org/accounts/
# Expected output: ["master_account", ...]You need credentials for both connectors stored in the same account profile. Skip any connector that's already configured.
curl -u admin:YOUR_PASSWORD \
https://api.metallorum.duckdns.org/accounts/master_account/credentialsLook for hyperliquid and derive (spot) or hyperliquid_perpetual and derive_perpetual (for funding rate arb) in the response.
curl -u admin:YOUR_PASSWORD \
-X POST \
-H "Content-Type: application/json" \
-d '{
"hyperliquid_api_key": "0xYOUR_WALLET_ADDRESS",
"hyperliquid_secret_key": "0xYOUR_PRIVATE_KEY"
}' \
https://api.metallorum.duckdns.org/accounts/add-credential/master_account/hyperliquidcurl -u admin:YOUR_PASSWORD \
-X POST \
-H "Content-Type: application/json" \
-d '{
"derive_api_key": "0xYOUR_WALLET_ADDRESS",
"derive_secret_key": "0xYOUR_PRIVATE_KEY"
}' \
https://api.metallorum.duckdns.org/accounts/add-credential/master_account/deriveNote for perpetual connectors: Use
hyperliquid_perpetualandderive_perpetualas the connector names in the credential endpoints when deploying the funding rate arbitrage variant.
Best for: Lower risk, market-neutral, no leverage required.
The bot monitors the SOL spot price on both exchanges in real time. When the price difference exceeds min_profitability (after fees), it simultaneously buys on the cheaper exchange and sells on the more expensive one. Capital is returned after each trade cycle.
| Exchange | Connector | Trading Pair | Role |
|---|---|---|---|
| Hyperliquid | hyperliquid |
SOL-USD |
Leg 1 |
| Derive | derive |
SOL-USDC |
Leg 2 |
Expected profit per trade: 0.2–0.5% gross, minus ~0.05–0.1% fees per side.
Capital required: ~2× position size (funds locked on both sides per executor).
Best for: Capturing sustained funding rate differentials between the two exchanges.
The bot opens opposing perpetual positions (long on the exchange paying funding, short on the one receiving it) and collects the net funding payment over time. Position PnL is approximately delta-neutral.
| Exchange | Connector | Trading Pair | Position |
|---|---|---|---|
| Hyperliquid | hyperliquid_perpetual |
SOL-USD |
Long or Short |
| Derive | derive_perpetual |
SOL-USD |
Opposite |
Funding intervals: Hyperliquid pays every 1 hour; Derive pays every 8 hours.
Key risk: Funding rates can flip — the bot will stop the position if the rate differential inverts past funding_rate_diff_stop_loss.
This strategy uses the
v2_funding_rate_arb.pyscript (not the controller framework). See Option B deployment steps below.
Create the controller config YAML via the API:
curl -u admin:YOUR_PASSWORD \
-X POST \
-H "Content-Type: application/json" \
-d '{
"content": "controller_name: arbitrage_controller\ncontroller_type: generic\nid: sol_arb_hl_derive\nexchange_pair_1:\n connector_name: hyperliquid\n trading_pair: SOL-USD\nexchange_pair_2:\n connector_name: derive\n trading_pair: SOL-USDC\nmin_profitability: 0.002\ntotal_amount_quote: 100\ndelay_between_executors: 10\nmax_executors_imbalance: 1\nrate_connector: kucoin\nquote_conversion_asset: USDT\n"
}' \
https://api.metallorum.duckdns.org/controllers/configs/sol_arb_hl_deriveEquivalent YAML content:
# bots/conf/controllers/sol_arb_hl_derive.yml
controller_name: arbitrage_controller
controller_type: generic
id: sol_arb_hl_derive
# Leg 1: Hyperliquid spot
exchange_pair_1:
connector_name: hyperliquid
trading_pair: SOL-USD
# Leg 2: Derive spot
exchange_pair_2:
connector_name: derive
trading_pair: SOL-USDC
# Strategy parameters
min_profitability: 0.002 # 0.2% minimum spread after fees to trigger
total_amount_quote: 100 # Position size in USD equivalent per executor
delay_between_executors: 10 # Seconds to wait between new executors
max_executors_imbalance: 1 # Max difference between buy-A and buy-B executors
# Rate oracle for USD/USDC cross-rate
rate_connector: kucoin
quote_conversion_asset: USDTParameter tuning guide:
| Parameter | Conservative | Balanced | Aggressive |
|---|---|---|---|
min_profitability |
0.004 (0.4%) |
0.002 (0.2%) |
0.001 (0.1%) |
total_amount_quote |
50 |
100 |
500 |
delay_between_executors |
30 |
10 |
5 |
Set min_profitability above your combined fee cost (~0.1% total). Lower values increase trade frequency but risk negative-PnL fills.
The v2_funding_rate_arb.py script accepts a YAML config. Create it:
# bots/conf/scripts/sol_funding_rate_arb.yml
script_file_name: v2_funding_rate_arb.py
leverage: 5 # Leverage on each perp position (keep low, 3–10x)
min_funding_rate_profitability: 0.001 # Min daily funding diff to enter (0.1%)
connectors:
- hyperliquid_perpetual
- derive_perpetual
tokens:
- SOL
position_size_quote: 200 # Size of each leg in USD
profitability_to_take_profit: 0.01 # Close position at 1% total PnL
funding_rate_diff_stop_loss: -0.001 # Stop if funding diff inverts past -0.1%
trade_profitability_condition_to_enter: falseImportant: The
v2_funding_rate_arb.pyscript'squote_markets_maphardcodeshyperliquid_perpetual → USDandbinance_perpetual → USDT. Since Derive perpetual also uses USD, you must ensure the trading pair resolves correctly. The script falls back toUSDTfor unknown connectors. IfSOL-USDTdoesn't exist on Derive, the connector will fail.Workaround: Copy
v2_funding_rate_arb.pytobots/scripts/in hummingbot-api and add"derive_perpetual": "USD"to thequote_markets_mapdict. The shared scripts volume will make it available to all bot instances.
curl -u admin:YOUR_PASSWORD \
-X POST \
-H "Content-Type: application/json" \
-d '{
"instance_name": "sol-arb-hl-derive",
"credentials_profile": "master_account",
"controllers_config": ["sol_arb_hl_derive"],
"max_global_drawdown_quote": 50,
"max_controller_drawdown_quote": 50,
"image": "hummingbot/hummingbot:latest",
"headless": true
}' \
https://api.metallorum.duckdns.org/bot-orchestration/deploy-v2-controllersResponse:
{
"success": true,
"unique_instance_name": "sol-arb-hl-derive-20260331-143000",
"message": "Bot deployed successfully"
}Save the unique_instance_name — you'll need it for monitoring and stopping.
What happens behind the scenes:
- hummingbot-api creates
bots/instances/sol-arb-hl-derive-20260331-143000/ - Copies credentials from
master_accountinto the instance - Copies
sol_arb_hl_derive.ymland generates a script config - Spawns a Docker container running
v2_with_controllers.pyin headless mode - The controller begins watching both order books immediately
The funding rate arb uses a standalone script. You need to spin up a hummingbot instance first, then start the script:
Step 1: Deploy a bare hummingbot instance via docker (no controller config):
# Start a hummingbot container manually via the docker endpoint
curl -u admin:YOUR_PASSWORD \
-X POST \
-H "Content-Type: application/json" \
-d '{
"instance_name": "sol-funding-arb",
"credentials_profile": "master_account",
"image": "hummingbot/hummingbot:latest",
"headless": false
}' \
https://api.metallorum.duckdns.org/bot-orchestration/deploy-v2-controllersStep 2: Start the funding rate arb script on the running instance:
curl -u admin:YOUR_PASSWORD \
-X POST \
-H "Content-Type: application/json" \
-d '{
"bot_name": "sol-funding-arb-20260331-143000",
"script": "v2_funding_rate_arb",
"conf": "sol_funding_rate_arb",
"log_level": "INFO"
}' \
https://api.metallorum.duckdns.org/bot-orchestration/start-botcurl -u admin:YOUR_PASSWORD \
https://api.metallorum.duckdns.org/bot-orchestration/sol-arb-hl-derive-20260331-143000/statusThe response includes:
performance: realized PnL, volume traded, open executorsgeneral_logs: recent log lineserror_logs: any errors
curl -u admin:YOUR_PASSWORD \
https://api.metallorum.duckdns.org/bot-orchestration/statuscurl -u admin:YOUR_PASSWORD \
"https://api.metallorum.duckdns.org/bot-orchestration/sol-arb-hl-derive-20260331-143000/history?days=1&verbose=true"| Signal | Meaning | Action |
|---|---|---|
| Executors firing frequently | Spread is available | Good — verify PnL is positive |
| Zero executors for >30 min | Spread below min_profitability |
Normal — consider lowering threshold |
| Error logs: "balance" | Insufficient funds on one exchange | Top up wallet |
| Error logs: "order not found" | Partial fill or race condition | Bot handles automatically |
| High imbalance (buy-only or sell-only) | One leg consistently cheaper | Normal during trends — resolves when spread closes |
curl -u admin:YOUR_PASSWORD \
-X POST \
-H "Content-Type: application/json" \
-d '{
"bot_name": "sol-arb-hl-derive-20260331-143000",
"skip_order_cancellation": false
}' \
https://api.metallorum.duckdns.org/bot-orchestration/stop-botcurl -u admin:YOUR_PASSWORD \
-X POST \
https://api.metallorum.duckdns.org/bot-orchestration/stop-and-archive-bot/sol-arb-hl-derive-20260331-143000| Parameter | Type | Default | Description |
|---|---|---|---|
exchange_pair_1 |
ConnectorPair | — | First exchange + trading pair |
exchange_pair_2 |
ConnectorPair | — | Second exchange + trading pair |
min_profitability |
Decimal | 0.01 |
Minimum net spread to trigger (after fees) |
total_amount_quote |
Decimal | — | Position size per executor in quote currency |
delay_between_executors |
int | 10 |
Cooldown (seconds) after each completed executor |
max_executors_imbalance |
int | 1 |
Max allowed difference between buy-A and buy-B executors |
rate_connector |
str | binance |
Exchange used for cross-rate quotes |
quote_conversion_asset |
str | USDT |
Common quote for cross-rate calculation |
| Parameter | Type | Default | Description |
|---|---|---|---|
leverage |
int | 20 |
Leverage applied on each perpetual position |
min_funding_rate_profitability |
Decimal | 0.001 |
Min expected daily funding diff to open position |
connectors |
Set[str] | — | Perpetual connectors (e.g., hyperliquid_perpetual,derive_perpetual) |
tokens |
Set[str] | — | Base tokens to arb (e.g., SOL) |
position_size_quote |
Decimal | 100 |
Size of each leg in USD |
profitability_to_take_profit |
Decimal | 0.01 |
Close position when total PnL reaches this |
funding_rate_diff_stop_loss |
Decimal | -0.001 |
Close position if daily funding diff inverts past this |
trade_profitability_condition_to_enter |
bool | False |
Also require positive trade PnL to enter |
| Exchange | Spot Connector | Perp Connector | SOL Trading Pair (spot) | SOL Trading Pair (perp) |
|---|---|---|---|---|
| Hyperliquid | hyperliquid |
hyperliquid_perpetual |
SOL-USD |
SOL-USD |
| Derive | derive |
derive_perpetual |
SOL-USDC |
SOL-USD |
Before going live:
- Verify both wallets are funded (SOL + quote asset on each exchange)
- Run with
total_amount_quote: 10for a few hours first to validate execution - Check that both connectors appear in your account credentials
- Confirm SOL-USD and SOL-USDC markets are active on both exchanges
- Set
max_global_drawdown_quoteto cap losses (suggested: 20–50% of capital)
Common issues:
Bot deploys but no trades:
- Spread is below
min_profitability. Lower to0.001temporarily to test execution, then set your real threshold. - Check logs for rate oracle errors (
Cannot get conversion rate for SOL-USDT). This meansrate_connector: kucoincan't fetch the pair — verify kucoin credentials are present.
"Insufficient balance" errors:
- The arbitrage executor needs funds on BOTH legs simultaneously. Each executor locks ~
total_amount_quoteUSD on each side. - Minimum recommended: 3–5×
total_amount_quoteper exchange to handle concurrent executors.
Orders get stuck / not filled:
- Both legs use
OrderType.MARKET— fills should be immediate. If you see stuck executors, check exchange connectivity.
Funding rate arb: derive_perpetual uses USDT pair:
- Symptom: "trading pair SOL-USDT does not exist on derive_perpetual"
- Fix: Add
"derive_perpetual": "USD"toquote_markets_mapin your custom copy ofv2_funding_rate_arb.py.
One exchange consistently cheaper:
- This is normal during trending markets. The bot correctly keeps firing in one direction. The
max_executors_imbalanceparameter prevents runaway capital concentration — keep it at1or2.
Monitoring funding rate profitability before entry:
You can check current funding rates without running the bot:
# Get funding rate info from market data endpoint
curl -u admin:YOUR_PASSWORD \
"https://api.metallorum.duckdns.org/market-data/funding-rates?connector=hyperliquid_perpetual&trading_pair=SOL-USD"
curl -u admin:YOUR_PASSWORD \
"https://api.metallorum.duckdns.org/market-data/funding-rates?connector=derive_perpetual&trading_pair=SOL-USD"A daily funding differential of >0.1% (annualized ~36%) is typically worth entering. Below 0.05% daily, fees may eat the profit.