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.

bash
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.

python
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.

python
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 data

Step 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.

python
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))
ResultReturnSharpe ratioMax drawdownPosition changesFees (share of equity)Funding paid
20/50 EMA rule with a 50-day trend filter38.4%1.12−23.7%773.46%0.67%
Buy and hold BTC−27.8%−0.57−44.4%1——
Output of the script above, run at 22:34 UTC on Nov 11, 2026 on 2,190 four-hour BTC candles from Nov 11, 2025 to Nov 11, 2026. Illustrative backtest of a simple rule on one market and one year, with fixed common settings; not a forecast, a trade signal or the record of any Senpi strategy.

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.

python
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.

python
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.

Try Senpi free
Share

Related articles