← Back to Blog

Claude Code on a VDS: Build and Test Crypto Trading and Alert Bots, Step by Step

Published · by RS Computers

Claude Code Crypto Automation

To build a crypto trading or analysis bot with Claude Code, run Claude Code on a VDS (a virtual dedicated server) and let it write, test and run the code on that server. Claude Code is Anthropic's coding agent. It reads your project, edits files and runs commands, while the model itself runs in Anthropic's cloud. The server gives the bot what a laptop can't: it runs all day, it has a fixed IPv4 address that exchanges can whitelist, and it has enough CPU cores to test strategies quickly. You don't need to trust a bot with your money to get value from this. The safest start is a bot that only watches the market and sends you Telegram alerts, while you keep the buy and sell buttons.

Key facts, checked on 9 October 2026:

What Claude Code does in this setup, and what it doesn't

Claude Code is a programmer that sits in your terminal. You describe what you want in plain words. It reads the files, writes a strategy or a script, runs the backtest, reads the result and fixes what broke. On a server that means you can say "add RSI to this strategy, backtest it on the last four months and compare it with simply holding the coins" and come back to a finished answer.

What it doesn't do is know where the price goes next. It works with the same public data you have. Its value is speed: an idea you would spend an evening coding is tested in minutes, and most ideas fail that test. That alone saves money. Our own first test below is a good example.

Why a VDS and not your laptop

Where the server is matters more than you think

Most exchanges decide what you may use from your verified residence. Some also look at where the API request comes from:

Exchange (EU entity)MiCA licence fromFreqtrade support
KrakenIreland, June 2025Supported (spot; history needs trade downloads)
OKX (MyOKX EEA)Malta, January 2025Supported (spot)
Bybit EUAustria, May 2025Supported (spot)
Gate EUMalta, September 2025Supported (spot)
BitvavoNetherlands, June 2025Listed, not officially supported
CoinbaseLuxembourg, June 2025Listed, missing some features

Licence dates come from ESMA's interim MiCA register (updated 30 September 2026). Freqtrade's status comes from its own list-exchanges command, version 2026.9. In Kosovo, the Law on Crypto-Assets (No. 08/L-295, published November 2024) puts the Central Bank of Kosovo in charge of licensing crypto operators. Check your own exchange's terms before you choose a city.

Step 1: prepare the server

We used Debian 13. Log in as root and install the tools, then create a normal user for the bot work. Claude Code and the bots should never run as root:

# as root
apt update
apt install -y sudo curl git tmux docker.io docker-compose python3-venv cron
useradd -m -s /bin/bash -G sudo,docker bot
passwd bot

Type a long password twice. From now on, connect as that user with ssh bot@YOUR_SERVER_IP. docker compose version should print 2.26 or later.

Step 2: install Claude Code and sign in over SSH

# as the bot user
curl -fsSL https://claude.ai/install.sh | bash
exit

The installer puts Claude Code in ~/.local/bin. On Debian that folder joins your PATH at the next login, which is why the last line logs you out. Connect again and check the installation:

# as the bot user, after logging in again
claude --version
claude doctor

We got 2.1.295 (Claude Code) and "No installation issues found". Now start a tmux session, so Claude Code keeps working when your SSH connection drops, and run it:

# as the bot user
tmux new -s claude
claude

On a server without a browser, Claude Code prints a sign-in link. Press c to copy it, open it on your own computer or phone, sign in with your Claude account, and paste the code shown back into the terminal. To leave Claude Code running, press Ctrl+B, then D. tmux attach -t claude brings you back to it later.

Step 3: lock your keys away before Claude Code sees the project

Claude Code reads files to do its work, so decide now what it may not read. Create a project folder with a settings file:

# as the bot user
mkdir -p ~/bot-project/.claude && cd ~/bot-project
cat > .claude/settings.json <<'EOF'
{
  "permissions": {
    "deny": [
      "Read(./.env)",
      "Read(./secrets/**)",
      "Read(~/.alerts.env)"
    ],
    "disableBypassPermissionsMode": "disable"
  }
}
EOF
claude doctor

If the file has a typo, claude doctor prints Invalid settings and the file's name, as it did when we broke it on purpose. The deny rules stop Claude Code's own tools from reading your key files. The last setting turns off the mode that skips every permission question. Anthropic's permissions documentation recommends that mode only for isolated containers.

The documentation is also clear about a limit: deny rules cover Claude Code's built-in tools and common shell commands, not a script that opens the file itself. For a stronger fence, install the sandbox tools with sudo apt install bubblewrap socat (bubblewrap 0.12.0 on Debian 13) and turn the sandbox on with /sandbox inside Claude Code. Then follow the exchange-side rules, which matter even more:

Step 4: a trading bot in dry-run with Freqtrade

Freqtrade handles the hard parts: exchange connections, order handling, backtesting and a test mode. Get it running before you involve Claude Code:

# as the bot user
mkdir ~/ft_userdata && cd ~/ft_userdata
curl -fsSL https://raw.githubusercontent.com/freqtrade/freqtrade/stable/docker-compose.yml -o docker-compose.yml
docker compose pull
docker compose run --rm freqtrade create-userdir --userdir user_data

The last command creates user_data with a sample strategy in it. Freqtrade's new-config command asks questions to build a configuration. This minimal one does the same job; save it as user_data/config.json with nano:

{
  "max_open_trades": 3,
  "stake_currency": "EUR",
  "stake_amount": 100,
  "tradable_balance_ratio": 0.99,
  "fiat_display_currency": "EUR",
  "dry_run": true,
  "dry_run_wallet": 1000,
  "cancel_open_orders_on_exit": false,
  "trading_mode": "spot",
  "entry_pricing": { "price_side": "same", "use_order_book": true, "order_book_top": 1 },
  "exit_pricing": { "price_side": "same", "use_order_book": true, "order_book_top": 1 },
  "exchange": {
    "name": "myokx",
    "key": "",
    "secret": "",
    "pair_whitelist": ["BTC/EUR", "ETH/EUR", "SOL/EUR"]
  },
  "pairlists": [{ "method": "StaticPairList" }],
  "bot_name": "lab-bot",
  "initial_state": "running",
  "internals": { "process_throttle_secs": 5 }
}

"dry_run": true with a pretend wallet of 1,000 EUR means nothing real happens. The key fields stay empty, because downloading prices and simulating trades needs no account. We used OKX's EEA exchange (myokx) because it serves EUR pairs and gives long price histories. Now download four months of candles and backtest the sample strategy:

# as the bot user, in ~/ft_userdata
docker compose run --rm freqtrade download-data --config user_data/config.json --timeframes 1h 5m --days 120
docker compose run --rm freqtrade backtesting --config user_data/config.json --strategy SampleStrategy --timerange 20260611-

The download took 41 seconds for three pairs. The backtest took 14 seconds and printed a long table. These were the lines that mattered:

Result, 11 June to 9 October 2026Value
Trades100, of which 91 winners
Bot's total profit+2.95% (29.45 EUR on 1,000)
Market change (holding the three coins)+53.32%
Worst single trade-10.27%
Mean profit p-value0.25 (not statistically meaningful)

A 91% win rate sounds great until you read the line below it: simply holding the coins did eighteen times better. This is why backtesting comes first. A bot that looks busy and wins most trades can still lose against doing nothing.

When you want the simulated bot to run around the clock, start it in the background with docker compose up -d. Its log shows Dry run is enabled. All trades are simulated. and a heartbeat every minute (docker compose logs --tail 20 freqtrade). Freqtrade's web interface, FreqUI, should stay on 127.0.0.1 as in the downloaded file. The REST API docs strongly recommend not exposing it to the internet, so reach it through an SSH tunnel.

Step 5: put Claude Code to work

Give Claude Code the project rules first. A CLAUDE.md file in the project folder is read at the start of every session:

# as the bot user
cat > ~/ft_userdata/CLAUDE.md <<'EOF'
# Rules for this project
- This is a Freqtrade project run with docker compose. Strategies live in user_data/strategies.
- Never set dry_run to false and never add API keys. A human does that.
- Every strategy change ends with a backtest. Report total profit, max drawdown,
  number of trades and the "Market change" line side by side.
- Optimise on one period and confirm on a later period the optimiser never saw.
- Explain each entry and exit rule in plain words in the strategy file.
EOF

Claude Code treats CLAUDE.md as guidance, not as a lock, which is why the settings file from step 3 does the hard blocking. Then start Claude Code in that folder and ask for real work. These are the kinds of requests it handles well:

The last request shows a good habit: ask for analysis first, then approve changes. In normal mode Claude Code asks before it edits a file or runs a command, so you see each step. As the third request shows, keep testing periods apart. A strategy tuned on the same data it is judged on looks far better than it will trade.

Check on it from your phone

Claude Code's Remote Control connects a session running on your server to the Claude app on your phone or to claude.ai. Inside tmux, run claude remote-control and open the link or QR code it shows. It works with Pro, Max, Team and Enterprise sign-ins, not with API keys, and it uses outbound HTTPS only, so no port on the server needs to be opened. A long backtest can finish while you are away, and you read the result on your phone.

Not ready to automate? Let the bot watch and tell you

Many people don't want software placing orders, and that is a reasonable position. A watcher bot gives you most of the benefit with none of that risk. It checks prices and news every few minutes and sends a Telegram message when something needs your attention:

You decide what, if anything, to do. This is a script of the kind Claude Code writes in a few minutes when you describe the alerts you want. It reads public data only and never trades:

# as the bot user
mkdir -p ~/analysis && cd ~/analysis
python3 -m venv .venv
. .venv/bin/activate
pip install ccxt
nano alerts.py

Paste this, then save with Ctrl+O, Enter and Ctrl+X:

#!/usr/bin/env python3
"""Alert bot: watches prices, RSI and headlines, and sends a Telegram message. It never trades."""
import json
import os
import pathlib
import time
import urllib.parse
import urllib.request
import xml.etree.ElementTree as ET

import ccxt

PAIRS = ["BTC/EUR", "ETH/EUR", "SOL/EUR"]
MOVE_ALERT = 5.0                 # alert when the price moves more than this many % in 24 hours
RSI_LOW, RSI_HIGH = 30, 70       # alert when RSI leaves this range
FEEDS = ["https://cointelegraph.com/rss"]
WORDS = ["hack", "exploit", "delist", "halt", "outage", "depeg", "insolven", "freeze", "lawsuit", "sec "]
SEEN = pathlib.Path.home() / ".alerts-seen.json"


def rsi(closes, period=14):
    gains = losses = 0.0
    for prev, cur in zip(closes[-period - 1:-1], closes[-period:]):
        gains += max(cur - prev, 0)
        losses += max(prev - cur, 0)
    return 100.0 if losses == 0 else 100 - 100 / (1 + gains / losses)


def market_alerts(seen):
    exchange = ccxt.myokx()
    today = time.strftime("%Y-%m-%d")
    found = []
    for pair in PAIRS:
        closes = [c[4] for c in exchange.fetch_ohlcv(pair, timeframe="1h", limit=100)]
        change = (closes[-1] / closes[-25] - 1) * 100
        value = rsi(closes)
        key = f"{today} {pair}"          # one market alert per pair per day
        if key not in seen and (abs(change) >= MOVE_ALERT or not RSI_LOW <= value <= RSI_HIGH):
            found.append(f"{pair}: {closes[-1]:.2f} EUR, 24h {change:+.1f}%, RSI {value:.0f}")
            seen.add(key)
    return found


def news_alerts(seen):
    found = []
    for url in FEEDS:
        req = urllib.request.Request(url, headers={"User-Agent": "alert-bot/1.0"})
        root = ET.fromstring(urllib.request.urlopen(req, timeout=15).read())
        for item in root.iter("item"):
            title = (item.findtext("title") or "").strip()
            if title and title not in seen and any(w in title.lower() + " " for w in WORDS):
                found.append(f"{title}\n{item.findtext('link')}")
            seen.add(title)
    return found


def send(text):
    token, chat = os.environ.get("TG_TOKEN"), os.environ.get("TG_CHAT")
    if not token or not chat:
        print(text)
        return
    data = urllib.parse.urlencode({"chat_id": chat, "text": text[:4000]}).encode()
    urllib.request.urlopen(f"https://api.telegram.org/bot{token}/sendMessage", data=data, timeout=15)


seen = set(json.loads(SEEN.read_text())) if SEEN.exists() else set()
lines = market_alerts(seen) + news_alerts(seen)
if lines:
    send("Market watch\n\n" + "\n\n".join(lines))
SEEN.write_text(json.dumps(sorted(seen)[-1000:]))

Run it once with python alerts.py. Without a Telegram token it prints instead of sending. Our first run printed SOL/EUR: 97.44 EUR, 24h -5.9%, RSI 34; a second run printed nothing, because each alert is sent once per pair per day and each headline only once. The RSI here uses simple averages, so it can differ slightly from your exchange's chart.

To receive the messages, create a bot with @BotFather in Telegram and find your chat ID; our Telegram bot hosting guide shows both steps. Put the two values in a private file and schedule the script every 15 minutes:

# as the bot user
cat > ~/.alerts.env <<'EOF'
export TG_TOKEN="YOUR_BOT_TOKEN"
export TG_CHAT="YOUR_CHAT_ID"
EOF
chmod 600 ~/.alerts.env
( crontab -l 2>/dev/null; echo '*/15 * * * * . $HOME/.alerts.env; $HOME/analysis/.venv/bin/python $HOME/analysis/alerts.py >> $HOME/alerts.log 2>&1' ) | crontab -
crontab -l

If ~/alerts.log shows HTTP Error 401: Unauthorized, the token is wrong. The alert file is also one of the paths blocked in step 3, so Claude Code won't read the token while it improves the script.

Freqtrade can do a middle step too. Add "telegram": {"enabled": true, "token": "...", "chat_id": "..."} to the config and keep "dry_run": true. The bot then messages you every simulated entry and exit, and you can decide whether to copy them by hand. With a wrong token, its log says the token "was rejected by the server", which tells you what to fix.

Exchange API limits, and how to stay inside them

Every exchange limits how often a bot may call its API. The limits are counted per IP address, and going over has consequences:

Practical rules that keep a bot healthy:

Using your /48 of IPv6 for news and data

An RS Computers server in Amsterdam or Dublin comes with a whole /48 of IPv6, not a single address. That is useful for the data side of a bot. When we checked today, Cointelegraph, CoinGecko's API, Reddit, Google News and the SEC's website all had IPv6 addresses. The trading APIs of Kraken, Bybit and Binance had IPv4 addresses only.

That leads to a clean split. Trade over your fixed IPv4, the address you whitelist on the exchange. Collect news and data over IPv6, from an address that never touches an exchange key. A busy news collector then never shares an address with the key that can trade. You can also give each data job its own address, which makes logs and blocklists easy to read. Show your prefix and add a second address from it:

# as the bot user
ip -br link
ip -6 addr show dev eth0 scope global
sudo ip -6 addr add 2001:db8:abcd::10/48 dev eth0
curl -6 --interface 2001:db8:abcd::10 https://api64.ipify.org

Replace eth0 with your interface name from the first line, and 2001:db8:abcd with the first three groups of your own prefix. The last command prints the new address. On our Amsterdam test server, a news feed fetched that way answered 200 from the extra address. An address added like this lasts until the next reboot. Be a polite client on every address: respect each site's limits and terms, and prefer official APIs and RSS feeds to scraping.

Which plan: sizing for backtests, not for Claude Code

Claude Code itself is light, because the model runs at Anthropic. What needs the server's power is your own work: backtests and optimisation use every core you give them, price history fills disk, and every bot and database holds memory.

PlanGood for
VDS Small (4 vCPU, 8 GB, 240 GB NVMe)Claude Code, an alert bot and one dry-run Freqtrade bot, with short backtests.
VDS Medium (8 vCPU, 16 GB, 480 GB NVMe)Regular hyperopt runs with 8 workers, several bots side by side, a database of collected prices and news.
VDS Large (16 vCPU, 32 GB, 960 GB NVMe)Optimising many pairs and timeframes at once, Freqtrade's machine learning add-on FreqAI, or several people's projects on one server.

All VDS plans run on KVM with a 10 Gb/s port, unmetered traffic, a fixed IPv4 address and IPv6. Prices are on the plans page, and you can move up a plan later with a short reboot. Our measurement gives a feel for the difference: the same 200-round hyperopt took 9.5 minutes with 2 workers and under 3.5 minutes with 8. For servers in Amsterdam, Dublin or Prishtina, see VPS in the Netherlands, VPS in Ireland and VPS in Kosovo.

Honest limits, and the scams to avoid

Frequently asked questions

Can Claude Code trade crypto for me?

Claude Code writes and runs code; it is not a trading service. It can build, test and improve a bot such as Freqtrade on your server, but the bot follows the rules you approve, and no tool can promise profits. Keep it in dry-run until the backtests and weeks of simulated trading convince you.

Do I need a large server to run Claude Code?

No. Claude Code needs about 4 GB of RAM, because the model runs in Anthropic's cloud. A bigger VDS pays off for backtests and optimisation: in our test, 8 workers finished the same job about 2.8 times faster than 2.

How do I connect Claude Code to my server?

Install it on the server over SSH with the official installer, sign in by copying the link it prints to your own browser, and run it inside tmux so it keeps working when you disconnect. Remote Control then lets you follow the session from the Claude app on your phone.

Can I use Binance from a server in the Netherlands?

Freqtrade's exchange notes say Binance restricts API access by the server's country and name the Netherlands, and Binance doesn't serve Dutch residents. Use an exchange licensed under MiCA for EU customers, such as Kraken, OKX, Bybit EU or Bitvavo, and check its terms for your country.

Why do exchanges want an IP whitelist on API keys?

A key locked to your server's IP address is useless to anyone who steals it. Binance only allows trading on keys with an IP restriction, and Bybit expires keys without one after 90 days, so a server with a fixed IPv4 address makes this easy.

Can a bot just send me alerts instead of trading?

Yes, and it is the safest way to start. A small script checks prices, RSI and headlines every 15 minutes and sends a Telegram message when a coin moves sharply or the news mentions a hack, a delisting or halted withdrawals. You decide what to do.

Our short version

Put Claude Code on a VDS as a normal user, block it from reading your key files, and start with Freqtrade in dry-run and an alert bot that only watches. Let Claude Code write and test strategies, always against simply holding the coins, and keep trading keys locked to the server's IPv4 with withdrawals off. To get a server for it, pick a VDS on the plans page or message us on Telegram, and we will help you choose a city that works with your exchange.

← All articles

Chat on Telegram