Python for algorithmic trading in crypto: a starter project on Hyperliquid
Updated Nov 12, 2026 · 9 min read
Build a crypto trading algorithm in Python: pull Hyperliquid candles and funding with pandas, code a signal, backtest it with fees and funding (pandas and vectorbt), and send orders with CCXT on testnet. Data, signal and backtest code tested Nov 11, 2026.
How do you start algorithmic trading with Python? Pull market data from an exchange API into pandas, turn your rule into a signal, backtest it with fees and funding, then send orders through a library such as CCXT, on testnet first. This page builds all four steps for Hyperliquid in about 80 lines of Python: a 20/50 EMA trend rule on 4-hour BTC candles. The data, signal and backtest code ran against the live API on Nov 11, 2026; the order code ran on testnet up to the order itself.
Key takeaways
- You need four pieces: data, a signal, a backtest that charges costs, and execution. requests and pandas, vectorbt and CCXT cover them.
- Hyperliquid’s info API is public: candles (the most recent 5,000 per market and interval) and hourly funding history need no key.
- Act on the next candle and charge 0.045% a side plus funding. Most first backtests skip one of those.
- The script’s simple rule returned 38.4% over the past year against −27.8% for holding BTC: one market, one year, which says little until it survives walk-forward and Monte Carlo tests.
- For live orders, use an API wallet that can trade but not withdraw, start on testnet, and post maker orders to pay the lower fee.
Run the same rule without a server: Senpi
Senpi publishes this guide. If you want the rule running rather than the code, describe it to Senpi, an AI Quant for Hyperliquid, in the app or from Claude, ChatGPT, Grok, Muse or Hermes through Senpi Connectors. The agent asks what to trade, how to exit and how much to risk, reads the rules back, and runs them as code on Senpi’s runtime 24/7. Or start from Moose, an open-source template built on a 20/50 EMA cross on the 4-hour chart with a daily trend filter; it adds its own exits and sets leverage from each coin’s volatility, so it behaves differently from this script.
- No server and no key to guard: the agent holds no key and cannot withdraw.
- Maker-first entries with a taker fallback, and 0% Senpi fee on paid plans (0.03% on the free trial).
- Stops and halts built in. Strategies built from a rule or a template carry the Dynamic Stop-Loss (DSL) from the first fill, and each can have its own daily-loss halt, which lifts at the next UTC midnight, or a drawdown halt, which waits until you resume it. Copy strategies carry at most a flat stop.
A strategy you confirm runs as code on Senpi’s runtime, and by default no AI model makes live trading decisions. Each strategy trades from its own strategy wallet; the agent holds no key and cannot withdraw. Senpi doesn’t run backtests, so test the rule with a script like the one below first; Simulations, paper trading with simulated money, are coming.
What you need
Python 3.9 or later and five packages. If vectorbt fails to import with a plotly error, install plotly 5 alongside it.
pip install requests pandas numpy vectorbt ccxt
# if vectorbt raises a plotly error on import:
pip install "plotly<6"Step 1: pull candles and funding from Hyperliquid
Both endpoints are public. Funding history comes back in pages, so the loop keeps asking from the last timestamp until nothing new returns. The last candle is still forming, so it is dropped.
import time
import numpy as np
import pandas as pd
import requests
INFO = "https://api.hyperliquid.xyz/info" # public, no key needed
def candles(coin="BTC", interval="4h", days=365):
end = int(time.time() * 1000)
req = {"coin": coin, "interval": interval, "startTime": end - days * 86_400_000, "endTime": end}
rows = requests.post(INFO, json={"type": "candleSnapshot", "req": req}, timeout=30).json()
df = pd.DataFrame(rows)
df.index = pd.to_datetime(df["t"], unit="ms")
return df[["o", "h", "l", "c", "v"]].astype(float).iloc[:-1] # drop the unfinished candle
def funding(coin="BTC", days=365):
start, out = int(time.time() * 1000) - days * 86_400_000, []
while True: # the API pages its history
page = requests.post(INFO, json={"type": "fundingHistory", "coin": coin, "startTime": start}, timeout=30).json()
if not page:
break
out += page
start = page[-1]["time"] + 1
f = pd.DataFrame(out)
return pd.Series(f["fundingRate"].astype(float).values, index=pd.to_datetime(f["time"], unit="ms"))
df = candles("BTC")
fund = funding("BTC")
print(len(df), "candles from", df.index[0], "to", df.index[-1], "|", len(fund), "funding hours")Step 2: turn the rule into a signal
Long when the 20-period EMA is above the 50-period EMA and the price is above its 50-day average; short in the mirror case; flat otherwise. The trend filter needs 300 four-hour candles before it has a value, so the rule waits.
fast = df["c"].ewm(span=20, adjust=False).mean()
slow = df["c"].ewm(span=50, adjust=False).mean()
trend = df["c"].rolling(300).mean() # 300 four-hour candles = 50 days
signal = pd.Series(0, index=df.index)
signal[(fast > slow) & (df["c"] > trend)] = 1 # long in an uptrend
signal[(fast < slow) & (df["c"] < trend)] = -1 # short in a downtrend
signal[trend.isna()] = 0 # no trades until the filter has dataStep 3: backtest it with fees and funding
The position is shifted one candle so each trade uses only information that existed when it was placed. Every change in position pays the taker fee, and every hour held pays or earns funding.
TAKER = 0.00045 # 0.045% per side, Hyperliquid base tier
ret = df["c"].pct_change().fillna(0)
pos = signal.shift(1).fillna(0) # act on the next candle: no lookahead
turnover = pos.diff().abs().fillna(pos.abs()) # 1 = open or close, 2 = flip long <-> short
fees = turnover * TAKER
fund_4h = fund.resample("4h").sum().reindex(df.index).fillna(0)
paid_funding = pos * fund_4h # longs pay positive funding, shorts receive it
strat = pos * ret - fees - paid_funding
def stats(r, bars_per_year=6 * 365):
equity = (1 + r).cumprod()
dd = (equity / equity.cummax() - 1).min()
sharpe = r.mean() / r.std() * np.sqrt(bars_per_year)
return {"return": round(equity.iloc[-1] - 1, 3), "sharpe": round(sharpe, 2), "max_drawdown": round(dd, 3)}
print("strategy", stats(strat), "| trades", int((turnover > 0).sum()),
"| fees", round(fees.sum(), 4), "| funding", round(paid_funding.sum(), 4))
print("buy and hold", stats(ret))| Result | Return | Sharpe ratio | Max drawdown | Position changes | Fees (share of equity) | Funding paid |
|---|---|---|---|---|---|---|
| 20/50 EMA rule with a 50-day trend filter | 38.4% | 1.12 | −23.7% | 77 | 3.46% | 0.67% |
| Buy and hold BTC | −27.8% | −0.57 | −44.4% | 1 | — | — |
Fees cost 3.5% of equity over the year and funding 0.67%. Read the result as a starting point: test it on other markets and periods, then use walk-forward and Monte Carlo tests before you trust it. The Sharpe ratio here uses √(6 × 365) because there are six four-hour candles a day, every day.
Step 4: the same test in vectorbt
vectorbt runs the same signals through a portfolio simulator and reports the usual statistics.
import vectorbt as vbt
pf = vbt.Portfolio.from_signals(
df["c"],
entries=signal == 1, exits=signal != 1,
short_entries=signal == -1, short_exits=signal != -1,
fees=TAKER, freq="4h",
)
print(pf.stats()[["Total Return [%]", "Max Drawdown [%]", "Total Trades", "Sharpe Ratio"]])Its numbers differ from step 3 (38.7% return, −18.5% max drawdown, 39 trades on this run) for three reasons: step 3 resizes the position to the same exposure every candle, while vectorbt holds the coins it bought until it trades, as a real account does; vectorbt counts a round trip as one trade; and it doesn’t charge funding unless you add it.
Step 5: send orders with CCXT, testnet first
CCXT’s Hyperliquid class signs orders with a wallet key. Use an API wallet: it can place orders for your account but can’t withdraw. Keep the key in an environment variable, never in the code. set_sandbox_mode(True) points CCXT at Hyperliquid’s testnet. The order is post-only, so it rests on the book and pays the maker fee or is cancelled.
import os
import ccxt
ex = ccxt.hyperliquid({
"walletAddress": os.environ.get("HL_ACCOUNT_ADDRESS"), # your Hyperliquid account address
"privateKey": os.environ.get("HL_API_WALLET_KEY"), # an API wallet's key: it can trade but not withdraw
})
ex.set_sandbox_mode(True) # Hyperliquid testnet until you're ready
ex.load_markets()
symbol = "BTC/USDC:USDC"
target = int(signal.iloc[-1]) # +1 long, -1 short, 0 flat
side = "buy" if target > 0 else "sell"
book = ex.fetch_order_book(symbol)
price = book["bids"][0][0] if side == "buy" else book["asks"][0][0] # rest on your side of the book
usd = 100 # position size in USDC
amount = float(ex.amount_to_precision(symbol, usd / price))
print("target", target, side, amount, "BTC @", price)
DRY_RUN = True # flip only after testing on testnet
if target != 0 and not DRY_RUN:
order = ex.create_order(symbol, "limit", side, amount, price, {"postOnly": True})
print(order["id"], order["status"])Not sent for this guide: the create_order call matches CCXT’s source, but no live or testnet order was placed with it. Run it on testnet first.
- Trade the difference. In production, read your current position first (ex.fetch_positions([symbol])) and only trade what is needed to reach the target; close with reduceOnly so an exit can’t open a new position.
- Market orders need a price. CCXT sends Hyperliquid market orders as limit orders capped 5% from the price you pass, by default.
- The official SDK (hyperliquid-python-sdk) does the same job without CCXT, if you only trade Hyperliquid.
Step 6: run it on a schedule
- Run just after each candle closes, for example from cron at a minute past every fourth hour (UTC), and make each run safe to repeat: it compares target and current position and does nothing if they match.
- Log every decision with the inputs that produced it, so you can compare live fills with the backtest (what is slippage).
- Add a kill switch: a daily-loss and drawdown limit that stops the script, and an alert when it does.
- Start on testnet, then go live small. Hyperliquid’s minimum order is $10.
Is Python good for algorithmic trading?
Yes, for most traders. pandas and NumPy handle the data, vectorbt and similar libraries run fast backtests, and CCXT or an exchange’s official SDK places orders. Python is slower than C++ or Rust, which matters for high-frequency market making, not for rules that act on hourly or 4-hour candles.
Which Python library is best for crypto trading?
It depends on the job: CCXT for one API across many exchanges, the official Hyperliquid SDK for Hyperliquid only, vectorbt for fast research, Freqtrade for a ready-made strategy runner, NautilusTrader for one codebase that backtests and trades live, and Hummingbot for market making.
Can I backtest crypto strategies in Python for free?
Yes. Hyperliquid’s info API serves candles and funding history for free, and pandas, NumPy and vectorbt are free. The limit is history: the API returns only the most recent 5,000 candles per market and interval.
How do I connect Python to Hyperliquid?
Read market data with plain HTTP requests to the public info endpoint. To trade, use CCXT’s hyperliquid class or the official hyperliquid-python-sdk, signing with an API wallet’s key, and test on Hyperliquid’s testnet first.
Is it safe to put my private key in a trading script?
Use an API wallet instead of your main wallet’s key: it can trade for your account but can’t withdraw. Keep it in an environment variable or a secrets manager, never in the code or a repository.
Can ChatGPT or Claude write a trading bot in Python?
They can write most of the code in this guide from a description. Check what they produce for lookahead, missing fees and funding, and order logic that doesn’t read your current position, then test on testnet before any real money.
Do I need to code to automate a trading strategy?
No. On Senpi you describe the rule in plain English or deploy a template, approve it, and Senpi runs it as code on its runtime with a stop from the first fill.
This is educational material, not financial advice. The backtest results are illustrative, and leveraged perps can lose money quickly.
Skip the server
Describe your rule in plain English. Senpi runs it 24/7, maker-first, with the stop attached from the first fill.
Related articles
What is revenge trading, and how do you stop it?
Revenge trading is trading bigger or faster to win back a loss. Why it happens, the recovery maths that makes it costly, what Hyperliquid data shows, and rules that stop it.
What is the Calmar ratio, and what is a good one?
The Calmar ratio divides annual return by max drawdown. Formula, how to read it, and the one-year Calmar ratio of BTC, ETH, SOL, HYPE, XRP and DOGE on Nov 9, 2026.
17 crypto trading strategies explained: momentum, mean reversion, breakout, carry, DCA, grid and more
17 crypto trading strategies, from the five core families (momentum, mean reversion, breakout, carry, market making) to day trading, DCA, grid and copy trading: the edge each trades, when it fails, the Senpi templates that run them, and live Hyperliquid readings on Nov 9, 2026.
How to read Hyperliquid market data: open interest, long/short ratio, funding and liquidations
What open interest, the long/short ratio, funding and liquidations tell you on Hyperliquid, where each number comes from, and how to read them together, with the ten largest perps on Nov 10, 2026.