Algorithmic Crypto Trading with Python
Build a Python trading bot that buys and sells memecoins on Alpaca.
Introduction
30 Second Summary
Crypto markets move 24/7 and the biggest gains happen in seconds. Most traders miss them because they're asleep, at work, or too emotional to pull the trigger at the right moment.
In this project, you will build a Python trading bot that connects to live crypto markets, watches for momentum breakouts on volatile memecoins, and executes trades automatically using Alpaca's paper trading with $100 of simulated capital.
What You'll Build
When you run the bot, it springs to life in your terminal, scanning live prices, calculating indicators, and placing real orders the moment a breakout fires.
By the end of this project, you'll have:
- A trading bot that autonomously buys and sells DOGE/USD on live market data, visible in both your terminal and Alpaca dashboard.
- Real-time momentum indicators (RSI and EMA) calculated from live candle data that tell the bot exactly when price action is strong enough to strike.
- Built-in risk management that automatically cuts losing trades at 5% and locks in winners at 15%, protecting your capital without you lifting a finger.
- Secret Mission: Build a multi-coin scanner that hunts across DOGE, SHIB, and PEPE simultaneously, attacking whichever coin is breaking out hardest.
Will this cost me real money?
Paper trading is completely free. You will use simulated money against live market prices for the entire project. If you choose to go live later, you deposit $100 of your own money and pay approximately 0.35% per trade in Alpaca crypto fees.
Before We Start
You are about to build a bot that trades real money the moment you flip a single switch.
Before any code, let's lock in exactly what you're building and why it matters.
Set Up Your Trading Environment
Your trading bot needs a connection to the markets before it can do anything. That connection comes through Alpaca, a commission-free trading platform with a Python SDK that handles authentication, order execution, and market data.
In this step, you'll create your Alpaca paper trading account, install the SDK, and write the first lines of code that prove your bot can talk to the exchange.
In this step, get ready to:
- Create an Alpaca Paper Only account and generate API keys.
- Install the alpaca-py SDK and pandas.
- Connect to Alpaca from Python and confirm your $100 paper balance.
Sign up for Alpaca and generate API keys
Alpaca offers a Paper Only account that uses real-time market data but simulated money. You only need an email address to create one.
- Open your browser and go to alpaca.markets.
- Click the sign-up button and create a Paper Only account using your email.
- Confirm your email address if prompted.
What is paper trading?
Paper trading simulates real trading with live market prices but fake money. Your orders execute against real-time quotes, so the experience is identical to live trading. The only difference is no actual money changes hands.
Once you are logged in to the Alpaca dashboard, you need to create a paper account with a $100 starting balance and generate your API keys.
- In the upper-left corner of the dashboard, click the account selector and choose your Paper Trading account.
- If you see the default $100,000 balance, delete this paper account and create a new one. Click the account number in the upper-left corner, go to Account Settings, and delete the existing paper account.
- Create a new paper account by clicking the account selector again and choosing Open New Paper Account. Set the starting balance to 100 when prompted.
- Navigate to the API Keys section on your paper trading dashboard.
- Click to generate a new API key. You will see your API Key ID and a Secret Key.
- Copy both keys and paste them somewhere safe (a notes app or text file). The Secret Key is shown only once.
Save your Secret Key now
Alpaca only displays the Secret Key once at the moment you generate it. If you navigate away without copying it, you will need to regenerate a new key pair.
Want to trade real money later?
While you are in the Alpaca dashboard, you can optionally start a live account application. This requires identity verification and takes 1-2 business days to approve. You do not need it to continue this project.
Install the SDK and create your project folder
The alpaca-py SDK gives your Python code access to Alpaca's trading and market data APIs. You also need pandas for working with price data as tables.
- Press Cmd+Space (macOS) or the Windows key (Windows) to open your search bar.
- Type Cursor and press Enter to open it.
- Open Cursor's integrated terminal by pressing Ctrl+` (backtick key, same shortcut on macOS and Windows).
- Move to your Desktop by running this command:
cd ~/Desktop
- Create your project folder and move into it by running these commands:
mkdir dead-presidents
cd dead-presidents
- Install the alpaca-py SDK and pandas by running:
pip3 install alpaca-py pandas
You should see pip downloading and installing packages. The output ends with a success message listing the installed packages.
Seeing a permission error?
If you see a permissions error, try running pip3 install --user alpaca-py pandas instead. This installs the packages to your user directory without needing admin access.
If pip3 is not found, make sure Python 3 is installed and on your PATH. Help me troubleshoot my pip3 installation
- Now open this folder in Cursor. Click File in the top menu bar.
- Click Open Folder.
- Navigate to your Desktop and select the dead-presidents folder, then click Open.
Create config.py and bot.py
You need two files: one to store your API credentials and trading parameters, and one to run the bot. Separating config from logic keeps your keys out of the main code.
- In the Cursor Explorer sidebar, right-click and select New File. Name it config.py.
- Paste the following code into config.py:
import os
API_KEY = os.environ.get("ALPACA_API_KEY", "your-api-key-here")
API_SECRET = os.environ.get("ALPACA_API_SECRET", "your-api-secret-here")
PAPER = True
SYMBOL = "DOGE/USD"
NOTIONAL = 20
STOP_LOSS_PCT = 0.05
TAKE_PROFIT_PCT = 0.15
CHECK_INTERVAL_SECONDS = 60
RSI_PERIOD = 14
EMA_SHORT = 9
EMA_LONG = 21
What does this config do?
The os.environ.get() call checks for environment variables first, then falls back to the placeholder strings. You will replace the placeholder strings with your actual Alpaca API keys.
The rest of the file defines your trading parameters: which coin to trade (DOGE/USD), how much to spend per trade ($20), and your risk limits (5% stop-loss, 15% take-profit).
- Replace your-api-key-here with the API Key ID you copied from the Alpaca dashboard.
- Replace your-api-secret-here with the Secret Key you copied.
- Save config.py.
Never share or commit your API keys
These keys control access to your trading account. If you ever push this project to GitHub, add config.py to a .gitignore file so your credentials stay private.
- Create another new file in the Explorer sidebar. Name it bot.py.
- Paste the following code into bot.py:
from alpaca.trading.client import TradingClient
from config import API_KEY, API_SECRET, PAPER
# Connect to Alpaca using your paper trading credentials
trading_client = TradingClient(API_KEY, API_SECRET, paper=PAPER)
# Fetch and print your account balance
account = trading_client.get_account()
print(f"Paper account cash balance: ${account.cash}")
What does this code do?
The TradingClient is the main entry point to Alpaca's trading API. Setting paper=PAPER (which is True in your config) routes all requests to the paper trading environment.
Calling get_account() fetches your account details from Alpaca's servers. The account.cash attribute tells you how much simulated money you have available to trade.
- Save bot.py.
Before you run this, what do you expect the output to show? Think about what balance you set when you created your paper account.
- Run the bot by entering this command in your Cursor terminal:
python3 bot.py
You should see your paper account cash balance of $100 printed to the terminal, confirming the SDK connects to Alpaca successfully.
Seeing a 403 Forbidden or authentication error?
- Double-check that you pasted the correct API Key and Secret into config.py. The Secret Key is especially easy to miss a character on.
- Make sure you are using your paper trading keys, not live keys. Paper and live accounts have separate credentials.
- If you recently deleted and recreated your paper account, you must use the new keys from the freshly created account.
Help me debug my Alpaca API connection error
✔️ Awesome, I've got everything!
Great. Make sure you have saved both config.py and bot.py.
ⓧ I'd like to double check the full code
Here is the full config.py:
import os
API_KEY = os.environ.get("ALPACA_API_KEY", "your-api-key-here")
API_SECRET = os.environ.get("ALPACA_API_SECRET", "your-api-secret-here")
PAPER = True
SYMBOL = "DOGE/USD"
NOTIONAL = 20
STOP_LOSS_PCT = 0.05
TAKE_PROFIT_PCT = 0.15
CHECK_INTERVAL_SECONDS = 60
RSI_PERIOD = 14
EMA_SHORT = 9
EMA_LONG = 21
Here is the full bot.py:
from alpaca.trading.client import TradingClient
from config import API_KEY, API_SECRET, PAPER
# Connect to Alpaca using your paper trading credentials
trading_client = TradingClient(API_KEY, API_SECRET, paper=PAPER)
# Fetch and print your account balance
account = trading_client.get_account()
print(f"Paper account cash balance: ${account.cash}")
Your bot can now talk to the exchange. Next up, you'll teach it to pull live price data so it can actually see what the market is doing.
Pull Live Market Data
Your bot can connect to Alpaca and confirm your $100 paper balance. That proves authentication works. But a trading bot that cannot see prices is blind.
Before the bot can make any trading decision, it needs to watch the market in real time. In this step, you will pull live hourly candle data for DOGE/USD and display it as a table so you can see exactly what the bot sees.
In this step, get ready to:
- Set up a crypto market data client that fetches live prices from Alpaca.
- Build a function that pulls 50 hourly candles for DOGE/USD and converts them into a pandas DataFrame.
- Print the last 20 candles showing real market price action in your terminal.
Set up the crypto data client
Alpaca separates trading from market data. Your TradingClient handles orders and account info. A separate CryptoHistoricalDataClient handles price data. The crypto data client does not require API keys.
- Replace all the code in bot.py with the following imports and client setup:
from datetime import datetime, timedelta, timezone
import pandas as pd
from alpaca.trading.client import TradingClient
from alpaca.data.historical import CryptoHistoricalDataClient
from alpaca.data.requests import CryptoBarsRequest
from alpaca.data.timeframe import TimeFrame
from config import API_KEY, API_SECRET, PAPER, SYMBOL
# Connect to Alpaca for trading and market data
trading_client = TradingClient(API_KEY, API_SECRET, paper=PAPER)
data_client = CryptoHistoricalDataClient()
What does this code do?
- The new imports bring in CryptoHistoricalDataClient for price data, CryptoBarsRequest to build data queries, and TimeFrame to specify hourly bars.
- datetime and timedelta let you calculate the time window for historical data.
- data_client is instantiated with no arguments because Alpaca's crypto market data is free and public.
- The config import now includes SYMBOL so the bot knows which coin to watch.
Build the candle fetcher and display live data
A "candle" (also called a "bar") is a snapshot of price action over a time period. Each candle records the open, high, low, and close prices plus volume. Your bot will use these to calculate momentum.
- Below the data_client line in bot.py, add the following function and call:
def get_candles(symbol, limit=50):
# Calculate the time window: now minus `limit` hours
end = datetime.now(timezone.utc)
start = end - timedelta(hours=limit)
# Request hourly candle data from Alpaca
request = CryptoBarsRequest(
symbol_or_symbols=symbol,
timeframe=TimeFrame.Hour,
start=start,
end=end
)
bars = data_client.get_crypto_bars(request)
# Convert each bar into a dictionary row
rows = []
for bar in bars[symbol]:
rows.append({
"timestamp": bar.timestamp,
"open": float(bar.open),
"high": float(bar.high),
"low": float(bar.low),
"close": float(bar.close),
"volume": float(bar.volume),
})
# Build a sorted DataFrame from the rows
df = pd.DataFrame(rows)
df = df.sort_values("timestamp").reset_index(drop=True)
return df
# Fetch candles and display the last 20
df = get_candles(SYMBOL)
print(df.tail(20).to_string())
What does this function do?
- get_candles calculates a time window (50 hours back from now) and requests hourly bars for the given symbol.
- It loops through each bar returned by Alpaca and extracts timestamp, open, high, low, close, and volume into a list of dictionaries.
- pd.DataFrame(rows) converts that list into a structured table. Sorting by timestamp ensures the most recent candle is at the bottom.
- df.tail(20).to_string() prints the 20 most recent candles as a readable table in your terminal.
Before you run this, what do you expect to see? Think about what 20 hourly candles for a live cryptocurrency would look like as a table.
- Save bot.py and run it with this command:
python3 bot.py
You should see a table with 20 rows, each showing a timestamp, open price, high, low, close, and volume for DOGE/USD. These are real prices from the live market.
Seeing an error instead of data?
- If you see ModuleNotFoundError, make sure you installed both packages with pip3 install alpaca-py pandas in Step 1.
- If you see KeyError on the symbol, check that config.py has SYMBOL = "DOGE/USD" with the slash separator. Alpaca requires the / between the coin and USD.
- If you see an empty DataFrame, the market may have had no volume in the last 50 hours (unlikely for DOGE). Try running again in a few minutes.
Still stuck? Help me debug why my Alpaca crypto data fetch is failing.
✔️ Awesome, I've got everything!
Great. Double check you have saved bot.py and can see the 20-row table of live DOGE candle data in your terminal.
ⓧ I'd like to double check the full code
Here is the complete bot.py at this point:
from datetime import datetime, timedelta, timezone
import pandas as pd
from alpaca.trading.client import TradingClient
from alpaca.data.historical import CryptoHistoricalDataClient
from alpaca.data.requests import CryptoBarsRequest
from alpaca.data.timeframe import TimeFrame
from config import API_KEY, API_SECRET, PAPER, SYMBOL
trading_client = TradingClient(API_KEY, API_SECRET, paper=PAPER)
data_client = CryptoHistoricalDataClient()
def get_candles(symbol, limit=50):
end = datetime.now(timezone.utc)
start = end - timedelta(hours=limit)
request = CryptoBarsRequest(
symbol_or_symbols=symbol,
timeframe=TimeFrame.Hour,
start=start,
end=end
)
bars = data_client.get_crypto_bars(request)
rows = []
for bar in bars[symbol]:
rows.append({
"timestamp": bar.timestamp,
"open": float(bar.open),
"high": float(bar.high),
"low": float(bar.low),
"close": float(bar.close),
"volume": float(bar.volume),
})
df = pd.DataFrame(rows)
df = df.sort_values("timestamp").reset_index(drop=True)
return df
And the final two lines at the bottom of the file:
df = get_candles(SYMBOL)
print(df.tail(20).to_string())
config.py is unchanged from Step 1.
Your bot can now see the market. It pulls live hourly price data and structures it into a clean table. Next up, you will turn this raw data into trading signals by calculating RSI and EMA indicators that tell the bot when to strike.
Build the Momentum Breakout Strategy
Your bot can see live price data for DOGE/USD. Raw candle data tells you what happened, but it cannot tell you when to buy. For that, you need momentum indicators that measure the speed and direction of price movement.
In this step, you will calculate two classic trading indicators from your candle data. RSI measures whether a coin is gaining or losing steam. EMA crossovers reveal when short-term momentum overtakes the long-term trend. Together, they form a breakout signal that tells the bot exactly when to strike.
In this step, get ready to:
- Calculate RSI and EMA indicators from live candle data.
- Build a signal function that detects momentum breakouts.
- Test the strategy against real-time DOGE/USD prices.
Calculate RSI and EMA indicators
RSI (Relative Strength Index) scores momentum from 0 to 100. A value between 55 and 75 means the coin is gaining strength without being overbought. EMA (Exponential Moving Average) smooths price data to reveal trends. When a short EMA crosses above a long EMA, the trend is turning bullish.
- In bot.py, find the import line at the top that starts with from config import:
- Find this line:
from config import API_KEY, API_SECRET, PAPER, SYMBOL
- Replace it with this updated line that adds the indicator settings:
from config import API_KEY, API_SECRET, PAPER, SYMBOL, RSI_PERIOD, EMA_SHORT, EMA_LONG
- Add the RSI calculation function below your get_candles function by pasting this code:
def calculate_rsi(df, period=14):
# Calculate price change between each candle
delta = df["close"].diff()
# Separate gains and losses into their own series
gain = delta.where(delta > 0, 0.0)
loss = (-delta).where(delta < 0, 0.0)
# Average gains and losses over the lookback period
avg_gain = gain.rolling(window=period, min_periods=period).mean()
avg_loss = loss.rolling(window=period, min_periods=period).mean()
# Relative Strength = average gain / average loss
rs = avg_gain / avg_loss
rsi = 100 - (100 / (1 + rs))
df["RSI_14"] = rsi
return df
What does this code do?
The RSI formula splits price changes into gains (up moves) and losses (down moves), averages each over 14 candles, then computes their ratio. The result is a score from 0 to 100.
Below 30 means oversold (too much selling). Above 70 means overbought (too much buying). Our bot targets the sweet spot of 55 to 75, where momentum is strong but the coin has not peaked yet.
Save bot.py. You will see RSI in action once you add the EMA function next.
- Add the EMA calculation function directly below calculate_rsi by pasting this code:
def calculate_ema(df, column="close", period=9, label=None):
if label is None:
label = f"EMA_{period}"
# Exponential moving average weights recent prices more heavily
df[label] = df[column].ewm(span=period, adjust=False).mean()
return df
What does this code do?
EMA gives more weight to recent candles, making it react faster to price changes than a simple average. You will use two EMAs: a 9-period (fast) and a 21-period (slow).
When the fast EMA crosses above the slow EMA, short-term momentum is outpacing the longer trend. That is one of your breakout conditions.
- Now replace the last two lines at the bottom of bot.py (the ones that fetch candles and print the table). Find these lines:
df = get_candles(SYMBOL)
print(df.tail(20).to_string())
- Replace them with this code that calls your new indicator functions and prints the results:
df = get_candles(SYMBOL)
df = calculate_rsi(df, RSI_PERIOD)
df = calculate_ema(df, period=EMA_SHORT, label="EMA_9")
df = calculate_ema(df, period=EMA_LONG, label="EMA_21")
latest = df.iloc[-1]
print(f"RSI_14: {latest['RSI_14']:.2f}")
print(f"EMA_9: {latest['EMA_9']:.6f}")
print(f"EMA_21: {latest['EMA_21']:.6f}")
What does this code do?
- Fetches 50 hourly candles, then pipes the DataFrame through RSI and both EMA calculations.
- Grabs the most recent candle (df.iloc[-1]) and prints the current indicator values so you can see what the market looks like right now.
- Save bot.py and run it to see your indicators calculated on live data:
python3 bot.py
You should see three lines printed with the current RSI and EMA values for DOGE/USD. The exact numbers depend on what the market is doing right now.
Seeing NaN values?
RSI needs at least 14 candles of data to produce a value. If you see nan, the API may have returned fewer bars than expected. Check that your system clock is correct and that you have internet connectivity.
If you see an ImportError mentioning RSI_PERIOD, make sure you updated the from config import line at the top of bot.py to include RSI_PERIOD, EMA_SHORT, and EMA_LONG.
help me debug my RSI and EMA indicator output
Add the breakout signal logic
Indicators on their own just describe the market. You need a function that combines them into a clear BUY or HOLD decision. A breakout signal fires when all three conditions align: RSI shows strong momentum, the fast EMA has crossed above the slow EMA, and the price has broken above the previous candle's high.
- Add the check_signal function below your calculate_ema function in bot.py:
def check_signal(df):
if len(df) < 2:
return "HOLD"
latest = df.iloc[-1]
previous = df.iloc[-2]
rsi = latest["RSI_14"]
ema_short = latest["EMA_9"]
ema_long = latest["EMA_21"]
close = latest["close"]
prev_high = previous["high"]
# If any indicator is missing, hold
if pd.isna(rsi) or pd.isna(ema_short) or pd.isna(ema_long):
return "HOLD"
# All three conditions must be true for a breakout
rsi_bullish = 55 <= rsi <= 75
ema_crossover = ema_short > ema_long
breakout = close > prev_high
if rsi_bullish and ema_crossover and breakout:
return "BUY"
return "HOLD"
What does this code do?
- Checks three breakout conditions on the most recent candle.
- RSI between 55 and 75: momentum is strong but not overheated.
- EMA_9 above EMA_21: short-term trend has crossed above the long-term trend.
- Close price above the previous candle's high: price is breaking out of its recent range.
- ALL three must be true simultaneously. If any one fails, the function returns HOLD and the bot stays patient.
- Add this line at the very end of bot.py, after the EMA_21 print statement:
print(f"Signal: {check_signal(df)}")
Save bot.py.
Test your strategy against live data
Before you run this, what do you think the signal will be? Most of the time crypto is NOT in a breakout. The bot is designed to be patient and only fire when all three conditions align at once.
- Run the bot to see your full strategy output:
python3 bot.py
You should see four lines: your RSI value, EMA_9, EMA_21, and the signal (BUY or HOLD).
What if it says HOLD?
HOLD is correct behavior most of the time. It means the market is not breaking out right now. Your bot is being disciplined. It will only say BUY when all three indicators converge, which might happen once every few hours or even days depending on market conditions.
If it says BUY, you caught a live breakout forming. Either way, the strategy is working. In the next step, you will connect it to real order execution so the bot can act on the signal.
Still seeing errors?
If you see NameError: name 'check_signal' is not defined, make sure the check_signal function is defined ABOVE the bottom section where you call it. Python reads top to bottom.
help me fix my breakout signal function
✔️ Awesome, I've got everything!
Great. Double check you have saved bot.py and your output shows RSI, EMA, and Signal values.
ⓧ I'd like to double check the full code
Your bot.py should now contain these sections in order. Scroll through your file and confirm each one is present:
- Import block at the top (including RSI_PERIOD, EMA_SHORT, EMA_LONG from config).
- Client setup (trading_client and data_client).
- The get_candles function (unchanged from Step 2).
- The calculate_rsi function (10 lines, adds RSI_14 column).
- The calculate_ema function (5 lines, adds an EMA column).
- The check_signal function (returns BUY or HOLD based on three conditions).
- Bottom section: fetches candles, calculates all indicators, and prints RSI_14, EMA_9, EMA_21, and Signal.
Your bot can now read the market and decide when momentum is surging. Next up, you will connect that BUY signal to real order execution so when the bot says go, money moves.
Execute Your First Trade
Your strategy can spot breakout signals, but all it does is print "BUY" or "HOLD" to the terminal. Text on a screen does not move money.
In this step, you'll wire the strategy to real order execution on Alpaca's paper trading platform. When you run the bot, it will place a live market order against your paper account and you'll confirm the position on the dashboard.
In this step, get ready to:
- Add a buy order function that spends $20 on DOGE/USD.
- Add a sell order function that liquidates your position.
- Execute a full round-trip trade (buy then sell) on your paper account.
Add the buy order function
Right now your bot calculates indicators and checks the signal. You need a function that takes a symbol and a dollar amount, builds a market order request, and submits it to Alpaca. For testing, you'll trigger the buy directly (ignoring the signal) to confirm money moves.
- In bot.py, find the import section at the top of the file. Locate this line:
from alpaca.trading.client import TradingClient
- Add these two new import lines directly below it:
from alpaca.trading.requests import MarketOrderRequest
from alpaca.trading.enums import OrderSide, TimeInForce
- Now find the config import at the bottom of the import block:
from config import API_KEY, API_SECRET, PAPER, SYMBOL, RSI_PERIOD, EMA_SHORT, EMA_LONG
- Add NOTIONAL to that import so the line reads:
from config import API_KEY, API_SECRET, PAPER, SYMBOL, NOTIONAL, RSI_PERIOD, EMA_SHORT, EMA_LONG
Why NOTIONAL?
Your config.py already defines NOTIONAL = 20. This means each trade spends exactly $20, which is 20% of your $100 paper balance. Importing it here keeps the dollar amount configurable in one place.
- Now delete all the test code at the bottom of bot.py (everything below the check_signal function). Replace it with this function and test call:
def place_buy_order(symbol, notional):
# Build a market order for a dollar amount of crypto
order_data = MarketOrderRequest(
symbol=symbol,
notional=notional,
side=OrderSide.BUY,
time_in_force=TimeInForce.GTC
)
order = trading_client.submit_order(order_data=order_data)
print(f"BUY ORDER PLACED: {order.id} | Status: {order.status}")
return order.id
# Test round-trip trade
place_buy_order(SYMBOL, NOTIONAL)
What does this code do?
MarketOrderRequest builds a market buy order. The notional parameter tells Alpaca to spend exactly $20 rather than specifying a coin quantity. This is how you buy a dollar amount of fractional crypto.
OrderSide.BUY sets the direction. TimeInForce.GTC (Good Til Cancelled) means the order stays open until it fills. Crypto trades on Alpaca are 24/7, so it fills almost instantly.
Before you run this, what do you think will happen? Will the order succeed on your paper account, or will something block it?
- Save bot.py and run it:
python3 bot.py
You should see output like BUY ORDER PLACED: <order-id> | Status: accepted. The order ID is a long UUID and the status confirms Alpaca received it.
Seeing a forbidden or authentication error?
- Double-check that your ALPACA_API_KEY and ALPACA_API_SECRET environment variables are set to your paper trading keys (not live keys).
- Confirm you are using the paper account's API keys from the Alpaca dashboard. Paper and live keys are different.
- If you see an insufficient balance error, reset your paper account balance to $100 in the Alpaca dashboard.
help me debug my Alpaca buy order error
Complete the round-trip with a sell order
You just bought $20 of DOGE on paper. Now confirm it on the dashboard and add the ability to sell it back.
- Open the Alpaca dashboard in your browser at app.alpaca.markets.
- Make sure your paper trading account is selected in the top left corner.
- Click Positions on the left sidebar. You should see a DOGE/USD position with a quantity and market value.
- Note the quantity shown (e.g. 85.23 DOGE). You will need this number to test the sell.
Why do I need the quantity?
You bought using a dollar amount (notional), so Alpaca calculated how many coins $20 bought at the current price. To sell, you specify the exact coin quantity you want to liquidate. The dashboard shows this number.
- Back in bot.py, add the place_sell_order function directly below place_buy_order (above the # Test round-trip trade comment):
def place_sell_order(symbol, qty):
# Sell a specific quantity of crypto
order_data = MarketOrderRequest(
symbol=symbol,
qty=qty,
side=OrderSide.SELL,
time_in_force=TimeInForce.GTC
)
order = trading_client.submit_order(order_data=order_data)
print(f"SELL ORDER PLACED: {order.id} | Status: {order.status}")
return order.id
What does this code do?
This mirrors place_buy_order but uses OrderSide.SELL and specifies qty instead of notional. You sell by quantity because you want to liquidate your entire position down to zero coins.
- Now update the test code at the bottom of bot.py. Find this line:
place_buy_order(SYMBOL, NOTIONAL)
- Replace it with a sell call using the quantity you noted from the dashboard:
place_sell_order(SYMBOL, [[DOGE_QTY="the DOGE quantity from your dashboard, e.g. 85.23"]])
- Save bot.py and run it:
python3 bot.py
You should see SELL ORDER PLACED: <order-id> | Status: accepted. Check the Positions page on your Alpaca dashboard. Your DOGE/USD position should now be gone.
Position still showing?
- Make sure the quantity you entered matches exactly what the dashboard showed. A mismatch means you sold a partial amount.
- Refresh the Alpaca dashboard. Positions update in real time but your browser tab may be stale.
- If you see an insufficient quantity error, you may have already sold in a previous run. Check the dashboard for your current holdings.
help me debug my Alpaca sell order
- Now revert the bottom of bot.py back to the buy call for future use. Replace the sell line with:
# Test round-trip trade
place_buy_order(SYMBOL, NOTIONAL)
- Save bot.py.
You have now completed a full round-trip trade. Your bot bought $20 of DOGE, then sold it all back. Both orders executed against live market prices on your paper account.
✔️ Awesome, I've got everything!
Great. Double check you have saved bot.py with both place_buy_order and place_sell_order functions, plus the buy test call at the bottom.
ⓧ I'd like to double check the full code
Your bot.py should contain the following sections in this order. Scroll up and confirm each one is present:
- Imports: datetime, pandas, TradingClient, MarketOrderRequest, OrderSide, TimeInForce, CryptoHistoricalDataClient, CryptoBarsRequest, TimeFrame, and config values including NOTIONAL.
- Client setup: trading_client and data_client created.
- get_candles function fetching hourly bars.
- calculate_rsi function computing 14-period RSI.
- calculate_ema function computing exponential moving average.
- check_signal function returning BUY or HOLD.
- place_buy_order function submitting a notional market buy.
- place_sell_order function submitting a quantity market sell.
- Test call at bottom: place_buy_order(SYMBOL, NOTIONAL).
config.py is unchanged from the previous step.
Your bot can now buy and sell crypto programmatically against live market prices. Next, you'll add risk management so it knows when to cut losses and when to take profits, then wrap everything in a loop so it runs autonomously.
Add Risk Management and Run the Bot
Your bot can spot breakouts and execute trades. But it has no way to protect your money once a position is open.
In this step, you'll add risk management rules that automatically cut losses and lock in gains. Then you'll wrap everything in a continuous loop that runs autonomously every 60 seconds.
In this step, get ready to:
- Add position tracking with automatic stop-loss and take-profit exits.
- Build a main loop that scans, trades, and prints a live dashboard every 60 seconds.
- Run the bot autonomously against live market data.
Update imports and add position tracking
The bot needs to check whether it already holds a position before deciding to buy or sell. It also needs the time module to sleep between loop cycles, and the new config values for stop-loss and take-profit percentages.
- In bot.py, find the very first line:
from datetime import datetime, timedelta, timezone
- Add this new line directly above it, so it becomes the first line in the file:
import time
- Now find the config import line:
from config import API_KEY, API_SECRET, PAPER, SYMBOL, NOTIONAL, RSI_PERIOD, EMA_SHORT, EMA_LONG
- Replace that entire line with this expanded version that pulls in the risk management settings:
from config import (
API_KEY, API_SECRET, PAPER, SYMBOL, NOTIONAL,
STOP_LOSS_PCT, TAKE_PROFIT_PCT, CHECK_INTERVAL_SECONDS,
RSI_PERIOD, EMA_SHORT, EMA_LONG
)
- Scroll to the very bottom of the file and delete these two test lines:
# Test round-trip trade
place_buy_order(SYMBOL, NOTIONAL)
- Delete both of those lines. The bot will no longer fire a buy on every run.
Now add the position-checking function. This queries Alpaca to see if you currently hold any of your target coin.
- At the bottom of bot.py (where the test lines used to be), add the get_position function by pasting this code:
def get_position(symbol):
try:
positions = trading_client.get_all_positions()
for pos in positions:
if pos.symbol == symbol:
return float(pos.qty)
except Exception:
pass
return 0.0
What does this code do?
- get_all_positions() asks Alpaca for every open position in your paper account.
- The loop checks each position's symbol attribute to find the one matching your target coin.
- If found, it returns the quantity as a float. If not found (or if an error occurs), it returns 0.0 so the bot knows it has no position.
- Save bot.py.
- Verify the new function connects to Alpaca by running this command:
python3 -c "from bot import get_position; print(get_position('DOGE/USD'))"
You should see a number printed. If you still have a position from your test trade, you'll see a non-zero quantity. If you sold it back, you'll see 0.0.
Seeing an import error?
If you see an error mentioning STOP_LOSS_PCT not being found, double check that your config.py still has all the variables from Step 1. The import names in bot.py must exactly match the variable names defined in config.py.
Help me debug my config import error
Build the autonomous main loop
The main loop is the brain of the bot. Every 60 seconds it fetches fresh data, recalculates indicators, and decides whether to buy, sell, or wait.
- Below the get_position function in bot.py, add the first half of the run_bot function by pasting this code:
def run_bot():
print("=" * 50)
print("DEAD PRESIDENTS BOT")
mode = "PAPER" if PAPER else "LIVE"
print(f"Mode: {mode} | Target: {SYMBOL}")
print(f"Position size: ${NOTIONAL} per trade")
print(f"Stop-loss: {STOP_LOSS_PCT * 100}% | Take-profit: {TAKE_PROFIT_PCT * 100}%")
print("=" * 50)
entry_price = None
holding = False
while True:
try:
df = get_candles(SYMBOL)
df = calculate_rsi(df, RSI_PERIOD)
df = calculate_ema(df, period=EMA_SHORT, label="EMA_9")
df = calculate_ema(df, period=EMA_LONG, label="EMA_21")
latest = df.iloc[-1]
current_price = latest["close"]
rsi = latest["RSI_14"]
ema_9 = latest["EMA_9"]
ema_21 = latest["EMA_21"]
position_qty = get_position(SYMBOL)
if position_qty > 0 and not holding:
holding = True
if entry_price is None:
entry_price = current_price
What does this code do?
- The function starts by printing a banner showing the bot's configuration: mode, target coin, position size, and risk thresholds.
- entry_price tracks what you paid. holding tracks whether you currently have a position open.
- Inside the while True loop, the bot fetches fresh candles and recalculates all indicators every cycle.
- It checks if Alpaca reports an open position. If one exists but the bot didn't know about it (e.g. from a restart), it syncs its internal state.
- Directly below that code (still inside the run_bot function), add the decision logic and entry point by pasting this code:
if holding and entry_price:
pnl_pct = (current_price - entry_price) / entry_price * 100
print(f"[HOLDING] Price: ${current_price:.6f} | Entry: ${entry_price:.6f} | P/L: {pnl_pct:+.2f}% | RSI: {rsi:.1f}")
if current_price <= entry_price * (1 - STOP_LOSS_PCT):
print("STOP-LOSS TRIGGERED! Selling position.")
place_sell_order(SYMBOL, position_qty)
holding = False
entry_price = None
elif current_price >= entry_price * (1 + TAKE_PROFIT_PCT):
print("TAKE-PROFIT HIT! Selling position.")
place_sell_order(SYMBOL, position_qty)
holding = False
entry_price = None
else:
signal = check_signal(df)
print(f"[SCANNING] Price: ${current_price:.6f} | RSI: {rsi:.1f} | EMA9: {ema_9:.6f} | EMA21: {ema_21:.6f} | Signal: {signal}")
if signal == "BUY":
print("BREAKOUT DETECTED! Placing buy order.")
place_buy_order(SYMBOL, NOTIONAL)
entry_price = current_price
holding = True
except Exception as e:
print(f"Error: {e}")
time.sleep(CHECK_INTERVAL_SECONDS)
if __name__ == "__main__":
run_bot()
What does this code do?
- If holding a position, it calculates your P/L percentage and checks two exit conditions: sell immediately if price drops 5% below entry (stop-loss), or sell if price rises 15% above entry (take-profit).
- If not holding, it checks the breakout signal. When all conditions align, it buys and records the entry price.
- The except block catches any API errors without crashing the bot.
- time.sleep(CHECK_INTERVAL_SECONDS) pauses 60 seconds between each cycle, then the loop repeats.
- The if __name__ == "__main__" guard means the bot only starts when you run the file directly.
Why 5% stop-loss and 15% take-profit?
This is a 1:3 risk-to-reward ratio. You risk $1 to potentially make $3. Even if only 1 in 3 trades wins, you break even. With better signal accuracy, you profit.
Memecoins are volatile enough to hit either threshold quickly. The 5% stop prevents a single bad trade from destroying your capital.
- Save bot.py.
Run the bot and watch it trade
Before you run this, what do you expect to see? Will the bot immediately buy, or will it scan and wait?
- Launch the bot by running this command:
python3 bot.py
You should see the bot print its configuration banner, then start its main loop. Every 60 seconds it prints a status line.
What should I see?
If the bot has no position, you'll see [SCANNING] lines showing the current price, RSI, EMA values, and signal status (likely HOLD). That is correct. The bot is being patient and waiting for real breakout conditions.
If you still hold DOGE from your test trade, you'll see [HOLDING] lines with your P/L percentage updating each cycle. If the price drops 5% below your entry, the bot will automatically sell.
- Let the bot run for at least 2-3 cycles (2-3 minutes) to confirm it loops correctly.
- Press Ctrl+C to stop the bot when you are done observing.
Going live with real money
When your Alpaca live account is verified (1-2 business days after application), change PAPER = True to PAPER = False in config.py and update your API keys to your live keys. Deposit $100 and the same code trades real money.
Bot crashes immediately?
- If you see an indentation error, make sure the second code block you pasted lines up at the same level as the code above it inside the while True loop (12 spaces for the if holding line).
- If you see a NameError for time, confirm that import time is the very first line in your file.
- If the bot prints an API error on each cycle, check that your API keys in config.py are still correct.
Help me debug my trading bot crash
✔️ Awesome, I've got everything!
Great. Double check you've saved your file, then move on.
ⓧ I'd like to double check the full code
Your bot.py should contain these sections in order. Scroll through your file and confirm each is present:
- import time as the very first line.
- All other imports (datetime, pandas, alpaca modules).
- The multi-line from config import (...) pulling in all 11 variables.
- trading_client and data_client setup lines.
- get_candles() function.
- calculate_rsi() function.
- calculate_ema() function.
- check_signal() function.
- place_buy_order() function.
- place_sell_order() function.
- get_position() function.
- run_bot() function with the while loop, stop-loss, take-profit, and signal logic.
- if __name__ == "__main__": run_bot() at the very end.
Make sure the old test trade lines are gone. There should be no place_buy_order(SYMBOL, NOTIONAL) floating outside a function.
Your bot is alive. It watches the market, spots breakouts, buys aggressively, and protects your capital with automatic exits. Next up, you'll extend it to scan multiple memecoins simultaneously in the Secret Mission.
Secret mission
Multi-Coin Scanner
Your bot watches one coin. But breakouts happen across the entire memecoin market. What if it could hunt across DOGE, SHIB, and PEPE simultaneously, score each coin's breakout strength, and attack whichever one is surging hardest?
Clean Up Your Resources
Clean Up Your Resources
Decide whether to keep your resources running, pause them to come back later, or delete them entirely. This project runs locally and uses a free paper trading account, so there are no ongoing costs unless you switched to live trading.
Cost warning
If you switched to live trading, your real $100 is at risk while the bot runs. Alpaca charges approximately 0.35% per crypto trade. A $20 buy and sell round-trip costs roughly $0.14 in fees.
If you have open positions on a live account, those positions can lose value even when the bot is stopped. Consider selling manually on the Alpaca dashboard before walking away.
Resources you used:
- Alpaca Paper Only account with API keys (free, no ongoing cost).
- Open crypto positions in your paper or live account.
- Local project files (config.py and bot.py).
- Installed Python packages (alpaca-py and pandas).
Keep everything running
No action needed. Choose this if you are still actively building or want the bot to keep trading on paper.
- Leave the bot running in your terminal. It will continue scanning for breakouts and managing positions.
- Your paper account balance stays as-is. Positions remain open indefinitely.
- If you switched to live, your $100 remains at risk and your stop-loss and take-profit rules will manage open positions.
- The alpaca-py and pandas packages remain installed on your machine.
Pause - I'll come back to this later
Shut down the bot process to free up your terminal. Your existing positions remain open but no new trades will execute.
- Press Ctrl+C in the terminal where the bot is running to stop it.
- Your open positions stay as they are on Alpaca. You can sell them manually from the Alpaca dashboard if you want to close them before pausing.
- To resume later, run python3 bot.py again from the same folder.
Delete - I don't want to use this again
Remove all project resources and close out your trading activity entirely.
- Press Ctrl+C in the terminal to stop the bot if it is still running.
- Sell any open positions on the Alpaca dashboard. Navigate to Positions, click on the asset you hold, and select Liquidate to sell it.
- If you are on a live account, withdraw your remaining funds from the Alpaca dashboard after all positions are sold.
- Delete your API keys from the Alpaca dashboard for security. Navigate to your API keys page and regenerate or remove them so the old keys are invalidated.
Delete your local project files. On macOS or Linux, run:
rm config.py bot.py
On Windows, run:
del config.py, bot.py
Uninstall the Python packages by running:
pip3 uninstall alpaca-py pandas -y
That's a Wrap!
That's a Wrap!
Nice work. You just built an autonomous crypto trading bot from scratch that executes real trades against live market prices.
You've learned how to:
- Connect to Alpaca's Trading API with Python, pull live crypto candle data, and execute paper trades that simulate real money against real-time market prices.
- Implement a momentum breakout strategy using RSI and EMA indicators calculated from live hourly candles to detect when a coin is surging.
- Add risk management with a 5% stop-loss and 15% take-profit, then run the bot in a continuous loop that is one line change away from trading your real $100.
- Secret Mission: Build a multi-coin scanner that compares DOGE, SHIB, and PEPE simultaneously and attacks whichever one has the strongest breakout signal.
Ready to quiz yourself?