Introduction
Crypto markets operate around the clock, every day of the year. By the time you wake up, prices may have moved 5% on a single piece of macro news. Human traders are limited by biology — they need sleep, make emotional decisions, and can only watch a handful of charts at once. Automated trading bots have none of these constraints.
A well-constructed trading bot does exactly what you tell it to, every time, without hesitation. It can scan dozens of markets simultaneously, respond to price movements in milliseconds, and enforce hard risk limits that a human trader might override in a moment of greed or panic. That consistency is worth more than most technical edges.
Python is the ideal language for building trading bots. Its ecosystem is mature (pandas, numpy, requests), the syntax stays readable even in complex logic, and deployment is straightforward. The Purple Flea Trading API gives you a clean REST interface to real perpetual futures markets: open and close leveraged positions, stream live prices, and query your account state, all from a handful of HTTP calls.
By the end of this tutorial you will have a fully functional bot that checks for momentum signals every minute, trades BTC, ETH, and SOL perpetuals with configurable leverage, enforces position size caps and daily loss limits, and runs unattended 24/7 on a server using pm2. Every code block is self-contained and copy-paste ready.
Prerequisites
You will need the following before we start writing code:
- Python 3.8 or higher — check with
python3 --version
- requests — the only third-party library required for the API calls
- A basic understanding of crypto markets — you should know what a long position, leverage, and funding rate mean; no quant background required
- A Purple Flea account — free to create, no KYC needed to start on testnet
Install the single dependency now:
pip install requests
That is genuinely all you need. No heavyweight SDKs, no async frameworks. The bot we build in this tutorial makes plain HTTP requests against the Purple Flea REST API, which means you can adapt every pattern here to any language that has an HTTP client.
Testnet first. Purple Flea provides a sandboxed testnet environment at trading.purpleflea.com with paper money. Run every step in this tutorial against testnet before committing real capital. The API endpoints and response shapes are identical between testnet and production.
The Purple Flea Trading API uses bearer token authentication. Every request must include your API key in the Authorization header. You obtain a key by posting your email to the registration endpoint — no password required at this stage, the key itself is your credential.
import requests
BASE_URL = "https://trading.purpleflea.com"
# POST your email to create an account and receive an API key
resp = requests.post(
f"{BASE_URL}/v1/auth/register",
json={"email": "you@example.com"},
timeout=10,
)
resp.raise_for_status()
data = resp.json()
api_key = data["api_key"]
print(f"Your API key: {api_key}")
# Store it safely — treat this like a password
# Add to your shell profile or a .env file:
# export PURPLEFLEA_API_KEY="pf_sk_..."
The response JSON contains a single field: api_key. Copy it and store it as an environment variable. Never hardcode it in source files or commit it to version control.
PURPLEFLEA_API_KEY=pf_sk_your_key_here
All subsequent examples read the key from the environment. Build the shared headers object once and reuse it everywhere:
import os
import requests
BASE_URL = "https://trading.purpleflea.com"
API_KEY = os.environ["PURPLEFLEA_API_KEY"]
HEADERS = {
"Authorization": f"Bearer {API_KEY}",
"Content-Type": "application/json",
}
# Quick connectivity check — raises immediately if credentials are wrong
def ping():
r = requests.get(f"{BASE_URL}/v1/markets", headers=HEADERS, timeout=10)
r.raise_for_status()
print("Connected. Markets available:", len(r.json()))
ping()
Before placing any orders you need to know what instruments are available, what their current prices are, and what funding rates you are paying to hold open positions. GET /v1/markets returns all of this in one call.
import requests, os
BASE_URL = "https://trading.purpleflea.com"
HEADERS = {"Authorization": f"Bearer {os.environ['PURPLEFLEA_API_KEY']}"}
resp = requests.get(f"{BASE_URL}/v1/markets", headers=HEADERS, timeout=10)
resp.raise_for_status()
markets = resp.json()
# Each market object looks like:
# {
# "coin": "BTC",
# "mark_price": 67432.10,
# "funding_rate": 0.0001 # per 8h, annualised ~10.95%
# }
# Print BTC, ETH, SOL specifically
target_coins = {"BTC", "ETH", "SOL"}
for m in markets:
if m["coin"] in target_coins:
funding_pct = m["funding_rate"] * 100
print(
f"{m['coin']:<6} mark={m['mark_price']:>12,.2f} "
f"funding={funding_pct:+.4f}% / 8h"
)
Sample output:
BTC mark= 67,432.10 funding=+0.0100% / 8h
ETH mark= 3,521.44 funding=+0.0083% / 8h
SOL mark= 187.92 funding=+0.0061% / 8h
Funding rate is the periodic payment exchanged between long and short holders. A positive rate means longs pay shorts — the market is leaning long. If you plan to hold a position for days, funding costs will eat into your PnL. Always factor it in when sizing trades.
Cache the market data in your bot and refresh it every few minutes rather than on every signal check — it reduces latency and avoids rate limits:
import time
_markets_cache = {}
_markets_ts = 0
CACHE_TTL = 120 # seconds
def get_markets():
global _markets_cache, _markets_ts
if time.time() - _markets_ts < CACHE_TTL:
return _markets_cache
resp = requests.get(f"{BASE_URL}/v1/markets", headers=HEADERS, timeout=10)
resp.raise_for_status()
_markets_cache = {m["coin"]: m for m in resp.json()}
_markets_ts = time.time()
return _markets_cache
def get_mark_price(coin: str) -> float:
return get_markets()[coin]["mark_price"]
With markets confirmed, open a BTC long. You specify the coin, direction, notional size in USD, and leverage multiplier. The API returns a position ID and the entry price at which you were filled.
import requests, os
BASE_URL = "https://trading.purpleflea.com"
HEADERS = {
"Authorization": f"Bearer {os.environ['PURPLEFLEA_API_KEY']}",
"Content-Type": "application/json",
}
resp = requests.post(
f"{BASE_URL}/v1/trade/open",
headers=HEADERS,
json={
"coin": "BTC",
"side": "long", # "long" or "short"
"size_usd": 100, # $100 notional (margin = $20 at 5x)
"leverage": 5,
},
timeout=10,
)
resp.raise_for_status()
result = resp.json()
position_id = result["position_id"]
entry_price = result["entry_price"]
print(f"Opened BTC long id={position_id} entry={entry_price:,.2f}")
Note the distinction between notional size and margin. When you open a $100 position at 5x leverage, you are controlling $100 worth of BTC but only putting up $20 of margin. A 1% price move in your favour returns $1 on a $20 margin investment — a 5% return on capital. A 1% adverse move costs $1 and erodes 5% of your margin. Leverage amplifies both gains and losses proportionally.
Liquidation risk. At 5x leverage your position is liquidated (forcibly closed at a loss) if the price moves approximately 18–20% against you, depending on the funding accrued. Always set a stop-loss well before the liquidation price. Step 7 covers this in detail.
GET /v1/trade/positions returns all your currently open positions, including unrealized PnL calculated against the latest mark price. Poll this endpoint to track how your trades are performing in real time.
import requests, os, time
BASE_URL = "https://trading.purpleflea.com"
HEADERS = {"Authorization": f"Bearer {os.environ['PURPLEFLEA_API_KEY']}"}
def get_positions():
r = requests.get(f"{BASE_URL}/v1/trade/positions", headers=HEADERS, timeout=10)
r.raise_for_status()
return r.json()
# Poll every 5 seconds and print a summary
while True:
positions = get_positions()
if not positions:
print("No open positions.")
else:
print(f"{'ID':<12} {'Coin':<6} {'Side':<6} {'Entry':>10} {'Mark':>10} {'PnL':>10}")
print("-" * 60)
for p in positions:
pnl_sign = "+" if p["unrealized_pnl"] >= 0 else ""
print(
f"{p['position_id']:<12} {p['coin']:<6} {p['side']:<6} "
f"{p['entry_price']:>10,.2f} {p['mark_price']:>10,.2f} "
f"{pnl_sign}{p['unrealized_pnl']:>9.2f}"
)
print()
time.sleep(5)
Each position object in the array contains:
position_id — the unique ID you need to close the trade
coin — the asset being traded
side — "long" or "short"
entry_price — the price at which you entered
mark_price — current fair-value price used for PnL and liquidation calculations
unrealized_pnl — floating profit or loss in USD, updated in real time
When your strategy says to exit, post the position ID to /v1/trade/close. The API closes the position at the current mark price and returns the realized PnL — positive for a profit, negative for a loss.
import requests, os
BASE_URL = "https://trading.purpleflea.com"
HEADERS = {
"Authorization": f"Bearer {os.environ['PURPLEFLEA_API_KEY']}",
"Content-Type": "application/json",
}
def close_position(position_id: str) -> dict:
resp = requests.post(
f"{BASE_URL}/v1/trade/close",
headers=HEADERS,
json={"position_id": position_id},
timeout=10,
)
resp.raise_for_status()
result = resp.json()
print(f"Closed {position_id} realized PnL: {result['pnl']:+.2f} USD")
return result
# Example usage after reading position_id from GET /v1/trade/positions:
# close_position("pos_abc123")
The response pnl field reflects the realized gain or loss in USD after fees are deducted. Track these values to calculate your total daily PnL and enforce the daily loss limit we add in Step 7.
Now for the actual strategy logic. Momentum is one of the most well-studied effects in financial markets: assets that have been rising recently tend to keep rising, and vice versa, over short to medium time horizons. We will use 1-hour price change as our signal:
- If BTC 1h change > +1% → go long
- If BTC 1h change < −1% → go short
- Otherwise → stay flat (no trade, or hold existing position)
The Purple Flea market endpoint gives us the current mark price. To compute the 1-hour change we compare it to the price we recorded 60 minutes ago. The bot keeps a rolling price history in memory for exactly this purpose.
import os, time, logging, collections
import requests
logging.basicConfig(level=logging.INFO, format="%(asctime)s %(levelname)s %(message)s")
BASE_URL = "https://trading.purpleflea.com"
HEADERS = {
"Authorization": f"Bearer {os.environ['PURPLEFLEA_API_KEY']}",
"Content-Type": "application/json",
}
COIN = "BTC"
MOMENTUM_WINDOW = 60 # minutes of price history to keep
THRESHOLD_PCT = 1.0 # 1% 1h change triggers a trade
SIZE_USD = 100 # notional size per trade
LEVERAGE = 5 # 5x leverage
class MomentumBot:
def __init__(self):
# Deque stores (timestamp, price) tuples; maxlen caps memory use
self.price_history = collections.deque(maxlen=MOMENTUM_WINDOW + 5)
self.open_position = None # dict with position_id, side
def fetch_price(self) -> float:
r = requests.get(f"{BASE_URL}/v1/markets", headers=HEADERS, timeout=10)
r.raise_for_status()
for m in r.json():
if m["coin"] == COIN:
return float(m["mark_price"])
raise ValueError(f"{COIN} not found in markets")
def get_signal(self) -> str | None:
# Need at least 60 data points (60 minutes of 1-min samples)
if len(self.price_history) < MOMENTUM_WINDOW:
return None
now_price = self.price_history[-1][1]
hour_price = self.price_history[-MOMENTUM_WINDOW][1]
change_pct = (now_price - hour_price) / hour_price * 100
logging.info(f"{COIN} 1h change: {change_pct:+.3f}%")
if change_pct > THRESHOLD_PCT: return "long"
if change_pct < -THRESHOLD_PCT: return "short"
return None
def open_trade(self, side: str):
resp = requests.post(
f"{BASE_URL}/v1/trade/open",
headers=HEADERS,
json={"coin": COIN, "side": side, "size_usd": SIZE_USD, "leverage": LEVERAGE},
timeout=10,
)
resp.raise_for_status()
data = resp.json()
self.open_position = {"position_id": data["position_id"], "side": side}
logging.info(f"Opened {side} id={data['position_id']} entry={data['entry_price']:,.2f}")
def close_trade(self):
if not self.open_position:
return
resp = requests.post(
f"{BASE_URL}/v1/trade/close",
headers=HEADERS,
json={"position_id": self.open_position["position_id"]},
timeout=10,
)
resp.raise_for_status()
pnl = resp.json()["pnl"]
logging.info(f"Closed id={self.open_position['position_id']} PnL={pnl:+.2f}")
self.open_position = None
return pnl
def tick(self):
price = self.fetch_price()
self.price_history.append((time.time(), price))
signal = self.get_signal()
if signal is None:
return # no clear signal, hold
# If already in the right direction, do nothing
if self.open_position and self.open_position["side"] == signal:
return
# Close existing position if direction changed
if self.open_position:
self.close_trade()
# Open new position in signal direction
self.open_trade(signal)
def run(self):
logging.info("Momentum bot started. Collecting price history...")
while True:
try:
self.tick()
except Exception as e:
logging.error(f"Tick error: {e}")
time.sleep(60)
if __name__ == "__main__":
MomentumBot().run()
The bot starts collecting price samples immediately. After 60 minutes of data it will fire its first signal. The tick() method is intentionally concise: fetch, record, evaluate, act. Each responsibility is isolated in its own method, making it easy to swap the strategy logic without touching the order management code.
A bot without risk management is a disaster waiting to happen. Even excellent strategies have losing streaks, and a single runaway loss can wipe out weeks of gains. We add three layers of protection:
- Maximum position size — cap the notional USD exposed to any single coin
- Daily loss limit — halt all trading if cumulative daily losses exceed a threshold
- Per-trade stop-loss — close a position automatically if unrealized loss exceeds a percentage of margin
import time, logging
class RiskManager:
"""
Enforces hard risk limits for the trading bot.
Call check_before_open() before every new trade.
Call update_pnl() after every position close.
Call check_stop_loss() on every tick while a position is open.
"""
def __init__(
self,
max_position_usd: float = 500, # max notional per coin, $500
daily_loss_limit: float = 50, # halt after $50 loss in one day
stop_loss_pct: float = 2.0, # close if unrealized loss > 2% of notional
):
self.max_position_usd = max_position_usd
self.daily_loss_limit = daily_loss_limit
self.stop_loss_pct = stop_loss_pct / 100
self.daily_pnl = 0.0
self.day_start = self._today()
self.trading_halted = False
def _today(self) -> str:
import datetime
return datetime.date.today().isoformat()
def _reset_if_new_day(self):
if self._today() != self.day_start:
logging.info(f"New day: resetting daily PnL (was {self.daily_pnl:+.2f})")
self.daily_pnl = 0.0
self.day_start = self._today()
self.trading_halted = False
def check_before_open(self, proposed_size_usd: float) -> bool:
"""Returns True if the trade is allowed to proceed."""
self._reset_if_new_day()
if self.trading_halted:
logging.warning("Trading halted: daily loss limit reached. Skipping trade.")
return False
if proposed_size_usd > self.max_position_usd:
logging.warning(
f"Trade rejected: size {proposed_size_usd} exceeds max {self.max_position_usd}"
)
return False
return True
def update_pnl(self, realized_pnl: float):
"""Call after each position close with the realized PnL."""
self._reset_if_new_day()
self.daily_pnl += realized_pnl
logging.info(f"Daily PnL updated: {self.daily_pnl:+.2f}")
if self.daily_pnl <= -self.daily_loss_limit:
self.trading_halted = True
logging.warning(
f"DAILY LOSS LIMIT HIT ({self.daily_pnl:+.2f}). Halting until tomorrow."
)
def check_stop_loss(
self,
entry_price: float,
current_price: float,
side: str,
size_usd: float,
) -> bool:
"""Returns True if the stop-loss is triggered and position should be closed."""
if side == "long":
raw_pnl = (current_price - entry_price) / entry_price * size_usd
else:
raw_pnl = (entry_price - current_price) / entry_price * size_usd
loss_pct = raw_pnl / size_usd
if loss_pct <= -self.stop_loss_pct:
logging.warning(
f"Stop-loss triggered: {loss_pct*100:.2f}% loss on {side} entry={entry_price}"
)
return True
return False
Good defaults to start with: max position $500 USD notional, daily loss limit $50, stop-loss at 2% of notional (which equals a 10% adverse move at 5x leverage). Adjust these after you understand your strategy's typical volatility profile.
Your local machine will not run the bot indefinitely. You need a server — a $6/month VPS works fine — and a process manager that restarts the bot automatically if it crashes. pm2 is the standard tool for this. Despite being a Node.js utility, it manages any process including plain Python scripts.
# Install pm2 globally (requires Node.js)
npm install -g pm2
# Start the bot
pm2 start bot.py \
--name purpleflea-bot \
--interpreter python3 \
--env PURPLEFLEA_API_KEY=pf_sk_your_key_here
# Save the process list so it restarts after server reboots
pm2 save
pm2 startup # follow the printed command to enable autostart
# Useful commands
pm2 logs purpleflea-bot # tail live logs
pm2 status # see all processes
pm2 restart purpleflea-bot # restart after a code change
pm2 stop purpleflea-bot # stop the bot
For cleaner configuration, use an ecosystem file instead of inline flags. This makes environment variable management explicit and keeps your deployment reproducible:
module.exports = {
apps: [
{
name: "purpleflea-bot",
script: "bot.py",
interpreter: "python3",
watch: false,
max_restarts: 10,
restart_delay: 5000, // 5s between restarts
env: {
PURPLEFLEA_API_KEY: "pf_sk_your_key_here",
},
},
],
};
pm2 start ecosystem.config.cjs
Note on the .cjs extension. If your project has "type": "module" in its package.json, use the .cjs extension for the pm2 ecosystem config so Node.js treats it as CommonJS and the module.exports syntax works correctly.
Complete combined bot
Here is the complete bot — strategy, risk management, API calls, and main loop — in a single self-contained file. Copy it, set your API key, and run it.
"""
Purple Flea Momentum Trading Bot
---------------------------------
Strategy : 1-hour momentum (BTC, ETH, SOL)
Risk : max position size, daily loss limit, per-trade stop-loss
Deploy : pm2 start bot.py --interpreter python3
Requires : requests (pip install requests)
Env var : PURPLEFLEA_API_KEY
"""
import os
import time
import logging
import datetime
import collections
import requests
# ── Config ────────────────────────────────────────────────────────────────────
BASE_URL = "https://trading.purpleflea.com"
API_KEY = os.environ["PURPLEFLEA_API_KEY"]
HEADERS = {
"Authorization": f"Bearer {API_KEY}",
"Content-Type": "application/json",
}
COINS = ["BTC", "ETH", "SOL"]
TICK_INTERVAL = 60 # seconds between each main loop iteration
MOMENTUM_WINDOW = 60 # lookback window in ticks (= 60 minutes at 1-min ticks)
THRESHOLD_PCT = 1.0 # minimum 1h change % to trigger a trade
SIZE_USD = 100 # notional USD per position
LEVERAGE = 5 # leverage multiplier
MAX_POS_USD = 500 # maximum notional per coin
DAILY_LOSS_LIMIT = 50 # halt trading after $50 daily loss
STOP_LOSS_PCT = 2.0 # close position if loss exceeds 2% of notional
MARKET_CACHE_TTL = 120 # seconds to cache /v1/markets response
logging.basicConfig(
level=logging.INFO,
format="%(asctime)s %(levelname)-8s %(message)s",
datefmt="%Y-%m-%d %H:%M:%S",
)
# ── API helpers ───────────────────────────────────────────────────────────────
_markets_cache: dict = {}
_markets_ts: float = 0
def fetch_markets() -> dict:
global _markets_cache, _markets_ts
if time.time() - _markets_ts < MARKET_CACHE_TTL:
return _markets_cache
r = requests.get(f"{BASE_URL}/v1/markets", headers=HEADERS, timeout=10)
r.raise_for_status()
_markets_cache = {m["coin"]: m for m in r.json()}
_markets_ts = time.time()
return _markets_cache
def get_mark_price(coin: str) -> float:
return float(fetch_markets()[coin]["mark_price"])
def open_position(coin: str, side: str) -> dict:
r = requests.post(
f"{BASE_URL}/v1/trade/open", headers=HEADERS,
json={"coin": coin, "side": side, "size_usd": SIZE_USD, "leverage": LEVERAGE},
timeout=10,
)
r.raise_for_status()
return r.json()
def close_position(position_id: str) -> float:
r = requests.post(
f"{BASE_URL}/v1/trade/close", headers=HEADERS,
json={"position_id": position_id}, timeout=10,
)
r.raise_for_status()
return r.json()["pnl"]
def get_positions() -> list:
r = requests.get(f"{BASE_URL}/v1/trade/positions", headers=HEADERS, timeout=10)
r.raise_for_status()
return r.json()
# ── Risk manager ──────────────────────────────────────────────────────────────
class RiskManager:
def __init__(self):
self.daily_pnl = 0.0
self.day_start = datetime.date.today().isoformat()
self.trading_halted = False
def _reset_if_new_day(self):
today = datetime.date.today().isoformat()
if today != self.day_start:
logging.info(f"New trading day. Previous daily PnL: {self.daily_pnl:+.2f}")
self.daily_pnl = 0.0
self.day_start = today
self.trading_halted = False
def can_trade(self) -> bool:
self._reset_if_new_day()
if self.trading_halted:
logging.warning("Trading halted: daily loss limit reached.")
return not self.trading_halted
def record_close(self, pnl: float):
self.daily_pnl += pnl
logging.info(f"Daily PnL: {self.daily_pnl:+.2f} (limit: -{DAILY_LOSS_LIMIT})")
if self.daily_pnl <= -DAILY_LOSS_LIMIT:
self.trading_halted = True
logging.warning("DAILY LOSS LIMIT HIT. No new trades until tomorrow.")
def is_stop_loss(self, entry: float, current: float, side: str) -> bool:
change = (current - entry) / entry
pnl_pct = change if side == "long" else -change
if pnl_pct <= -(STOP_LOSS_PCT / 100):
logging.warning(f"Stop-loss: {pnl_pct*100:.2f}% on {side} entry={entry:.2f}")
return True
return False
# ── Main bot ──────────────────────────────────────────────────────────────────
class MomentumBot:
def __init__(self):
self.risk = RiskManager()
# price_history[coin] = deque of (timestamp, price)
self.price_history: dict[str, collections.deque] = {
coin: collections.deque(maxlen=MOMENTUM_WINDOW + 5)
for coin in COINS
}
# open_positions[coin] = {position_id, side, entry_price}
self.open_positions: dict[str, dict] = {}
def _signal(self, coin: str) -> str | None:
hist = self.price_history[coin]
if len(hist) < MOMENTUM_WINDOW:
return None
now_p = hist[-1][1]
prev_p = hist[-MOMENTUM_WINDOW][1]
chg = (now_p - prev_p) / prev_p * 100
logging.debug(f"{coin} 1h chg: {chg:+.3f}%")
if chg > THRESHOLD_PCT: return "long"
if chg < -THRESHOLD_PCT: return "short"
return None
def _maybe_stop_loss(self, coin: str, current_price: float):
pos = self.open_positions.get(coin)
if not pos:
return
if self.risk.is_stop_loss(pos["entry_price"], current_price, pos["side"]):
pnl = close_position(pos["position_id"])
logging.info(f"[{coin}] stop-loss close PnL={pnl:+.2f}")
self.risk.record_close(pnl)
del self.open_positions[coin]
def tick(self):
markets = fetch_markets()
for coin in COINS:
if coin not in markets:
continue
price = float(markets[coin]["mark_price"])
self.price_history[coin].append((time.time(), price))
# 1. Check stop-loss on open positions
self._maybe_stop_loss(coin, price)
# 2. Get momentum signal
if not self.risk.can_trade():
continue
signal = self._signal(coin)
if signal is None:
continue
pos = self.open_positions.get(coin)
# Already in the right direction — hold
if pos and pos["side"] == signal:
continue
# Close existing position if direction reversed
if pos:
pnl = close_position(pos["position_id"])
logging.info(f"[{coin}] closed {pos['side']} PnL={pnl:+.2f}")
self.risk.record_close(pnl)
del self.open_positions[coin]
# Recheck after close (daily limit may have triggered)
if not self.risk.can_trade():
continue
# Open new position
result = open_position(coin, signal)
self.open_positions[coin] = {
"position_id": result["position_id"],
"side": signal,
"entry_price": result["entry_price"],
}
logging.info(
f"[{coin}] opened {signal} id={result['position_id']} "
f"entry={result['entry_price']:,.2f}"
)
def run(self):
logging.info("Bot started. Coins: %s Threshold: %.1f%% Leverage: %dx",
COINS, THRESHOLD_PCT, LEVERAGE)
logging.info("Collecting %d minutes of price history before first signal...",
MOMENTUM_WINDOW)
while True:
try:
self.tick()
except requests.RequestException as e:
logging.error("Network error: %s (retrying next tick)", e)
except Exception as e:
logging.exception("Unexpected error: %s", e)
time.sleep(TICK_INTERVAL)
if __name__ == "__main__":
MomentumBot().run()
Save the file as bot.py, export your API key, and launch it:
export PURPLEFLEA_API_KEY=pf_sk_your_key_here
python3 bot.py
You will see log output like:
2026-02-27 09:00:00 INFO Bot started. Coins: ['BTC', 'ETH', 'SOL'] Threshold: 1.0% Leverage: 5x
2026-02-27 09:00:00 INFO Collecting 60 minutes of price history before first signal...
2026-02-27 09:01:00 INFO Market cache refreshed. 24 coins available.
2026-02-27 10:00:00 INFO [BTC] 1h chg: +1.43%
2026-02-27 10:00:01 INFO [BTC] opened long id=pos_7a2f1c entry=67,812.50
2026-02-27 11:00:00 INFO [BTC] 1h chg: +0.31%
2026-02-27 12:00:00 INFO [BTC] 1h chg: -1.18%
2026-02-27 12:00:01 INFO [BTC] closed long PnL=+4.72
2026-02-27 12:00:01 INFO Daily PnL: +4.72 (limit: -50)
2026-02-27 12:00:02 INFO [BTC] opened short id=pos_9c3d8e entry=67,680.00
What to do next
You now have a working trading bot. Here are the most valuable directions to take it further:
Add more trading pairs
The bot already supports multiple coins — it is iterating over the COINS list. Add "DOGE", "AVAX", or any coin returned by GET /v1/markets. Each coin runs its own independent momentum calculation and maintains its own open position. The risk manager's daily loss limit applies across the entire portfolio.
Backtest before going live
Download historical price data, replay it through _signal(), and measure what the strategy would have returned. A proper backtest accounts for fees (typically 0.05% per trade on perpetuals), funding costs, slippage, and the market impact of your own orders. Python's pandas library makes this straightforward. Start small on testnet, then compare live results against your backtest expectations before increasing size.
Connect to LangChain for AI-driven signals
LangChain makes it easy to build an LLM agent that reads on-chain data, news sentiment, and social signals alongside price data, then reasons about what position to take. The LangChain crypto tools guide walks through wiring a Claude-backed agent to the Purple Flea API. You keep the same order execution and risk management code built here; only the signal generation changes.
Improve the strategy itself
Momentum works but it is simple. Consider adding a volatility filter (skip trades when ATR is unusually low or high), a trend confirmation indicator (only take long signals when price is above a 200-period moving average), or a funding rate filter (avoid longs when funding is deeply positive). Each filter reduces trade frequency but tends to improve the quality of signals that do fire.
The complete Purple Flea Trading API reference is at trading.purpleflea.com/docs. The endpoint list in this tutorial covers everything you need to build production-grade bots; additional endpoints expose historical candles, liquidation prices, and account-level PnL summaries. Questions? Reach the team on the Purple Flea Discord.