The simplest free setup is Python + Alpaca's free paper-trading account + alpaca-py + scikit-learn. This guide builds a long-only daily stock bot for 1 symbol. It estimates whether the next daily candle will close higher, tests the strategy on historical data, and can submit simulated orders through Alpaca.
Start with paper trading. Alpaca provides a paper-trading environment at no cost, and its Basic market-data plan includes essential equity data from the IEX exchange. Paper trading does not fully reproduce slippage, market impact, latency or order-queue behavior.
The bot in this guide does not use leverage, short selling or real money.
Free AI Trading Bot Stack
| Component | Free Option | Purpose |
|---|---|---|
| Programming language | Python | Runs the bot |
| Market data | Alpaca Basic data | Supplies historical daily bars |
| Trading API | Alpaca paper trading | Simulates orders |
| Machine learning | scikit-learn | Trains the prediction model |
| Data analysis | pandas and NumPy | Creates indicators and calculations |
| Hosting | Your computer | Runs the bot once per day |
| Cost | $0 for this paper-trading setup | Live trading may involve fees, spreads and losses |
The model is a random forest classifier. It does not ask ChatGPT to predict prices. It learns patterns from historical features such as returns, moving-average distance, volatility and RSI.
Scikit-learn describes a random forest as an ensemble of decision-tree classifiers that averages multiple trees.
What the Free Bot Will Do
The bot will:
- Download daily historical data for one symbol.
- Calculate technical features.
- Label each day based on whether the next day closed higher.
- Train a random forest model.
- Test the model on later data that was not used for training.
- Print the strategy return and maximum drawdown.
- Estimate the probability of an upward move.
- Optionally buy or close a paper position.
The example uses SPY as a placeholder. It is not a recommendation to trade SPY.
Step 1: Install Python and the Free Libraries
Create a project folder and a virtual environment. Python's built-in venv module creates an isolated environment for the project's packages.
macOS or Linux
mkdir ai-trading-bot
cd ai-trading-bot
python3 -m venv.venv
source.venv/bin/activate
pip install alpaca-py pandas numpy scikit-learn
Windows PowerShell
mkdir ai-trading-bot
cd ai-trading-bot
python -m venv.venv.venv\Scripts\Activate.ps1
pip install alpaca-py pandas numpy scikit-learn
Alpaca's Python SDK is called alpaca-py. It supports trading clients, historical stock-data clients and paper trading through TradingClient(..., paper=True).
Step 2: Create Free Paper-Trading API Keys
Create an Alpaca paper-trading account and generate a paper API key and secret key from the dashboard.
Use paper keys, not live keys. Alpaca uses separate paper and live environments, with different API domains and credentials.
Set the credentials as environment variables.
macOS or Linux
export ALPACA_API_KEY="your_paper_api_key"
export ALPACA_SECRET_KEY="your_paper_secret_key"
Windows PowerShell
$env:ALPACA_API_KEY="your_paper_api_key"
$env:ALPACA_SECRET_KEY="your_paper_secret_key"
Do not paste API keys into public GitHub repositories, notebooks or prompts. Do not give live trading credentials to an AI assistant.
Step 3: Create the Machine-Learning Trading Bot
Create a file named bot.py and add this code:
import os
from datetime import datetime, timedelta, timezone
import numpy as np
import pandas as pd
from sklearn.ensemble import RandomForestClassifier
from alpaca.data.historical import StockHistoricalDataClient
from alpaca.data.requests import StockBarsRequest
from alpaca.data.timeframe import TimeFrame
from alpaca.trading.client import TradingClient
from alpaca.trading.enums import OrderSide, TimeInForce
from alpaca.trading.requests import MarketOrderRequest
# -----------------------------
# Configuration
# -----------------------------
API_KEY = os.environ["ALPACA_API_KEY"]
SECRET_KEY = os.environ["ALPACA_SECRET_KEY"]
SYMBOL = os.getenv("SYMBOL", "SPY")
# Keep this False until you have reviewed the backtest.
RUN_PAPER_ORDER = False
# Maximum notional value of a new paper position.
POSITION_VALUE = 100.00
FEATURES = [
"return_1d",
"return_5d",
"sma_10_ratio",
"sma_30_ratio",
"volatility_10d",
"rsi_14",
]
# -----------------------------
# Technical indicators
# -----------------------------
def calculate_rsi(series, period=14):
delta = series.diff()
gains = delta.clip(lower=0)
losses = -delta.clip(upper=0)
average_gain = gains.rolling(period).mean()
average_loss = losses.rolling(period).mean()
relative_strength = average_gain / average_loss.replace(0, np.nan)
return 100 - (100 / (1 + relative_strength))
def create_features(df):
data = df.copy()
data["return_1d"] = data["close"].pct_change()
data["return_5d"] = data["close"].pct_change(5)
data["sma_10_ratio"] = (
data["close"] / data["close"].rolling(10).mean()
) - 1
data["sma_30_ratio"] = (
data["close"] / data["close"].rolling(30).mean()
) - 1
data["volatility_10d"] = data["return_1d"].rolling(10).std()
data["rsi_14"] = calculate_rsi(data["close"], 14)
return data.dropna()
# -----------------------------
# Download historical data
# -----------------------------
def load_daily_bars(days=365 * 5):
data_client = StockHistoricalDataClient(API_KEY, SECRET_KEY)
request = StockBarsRequest(
symbol_or_symbols=SYMBOL,
timeframe=TimeFrame.Day,
start=datetime.now(timezone.utc) - timedelta(days=days),
)
bars = data_client.get_stock_bars(request)
df = bars.df
# Alpaca returns a multi-index DataFrame when requesting symbols.
if isinstance(df.index, pd.MultiIndex):
df = df.xs(SYMBOL, level="symbol")
return df.sort_index()
# -----------------------------
# Prepare labelled data
# -----------------------------
raw_data = load_daily_bars()
feature_data = create_features(raw_data)
labelled_data = feature_data.copy()
# Target: whether the next day's close is higher.
labelled_data["target"] = (
labelled_data["close"].shift(-1) > labelled_data["close"]
).astype(float)
# The final row has no known next-day outcome.
labelled_data.loc[
labelled_data["close"].shift(-1).isna(),
"target"
] = np.nan
labelled_data = labelled_data.dropna(subset=["target"])
# -----------------------------
# Chronological train/test split
# -----------------------------
split_index = int(len(labelled_data) * 0.80)
train_data = labelled_data.iloc[:split_index]
test_data = labelled_data.iloc[split_index:].copy()
model = RandomForestClassifier(
n_estimators=300,
max_depth=5,
min_samples_leaf=5,
class_weight="balanced",
random_state=42,
n_jobs=-1,
)
model.fit(
train_data[FEATURES],
train_data["target"].astype(int),
)
# -----------------------------
# Backtest
# -----------------------------
test_data["probability_up"] = model.predict_proba(
test_data[FEATURES]
)[:, 1]
# Enter long when confidence is at least 55%.
# Exit when confidence is 45% or lower.
# Otherwise keep the previous position.
raw_signal = np.where(
test_data["probability_up"] >= 0.55,
1,
np.where(test_data["probability_up"] <= 0.45, 0, np.nan),
)
test_data["signal"] = (
pd.Series(raw_signal, index=test_data.index).ffill().fillna(0)
)
test_data["asset_return"] = test_data["close"].pct_change()
# Shift the signal by one row so today's signal applies
# to the following day's return.
test_data["strategy_return"] = (
test_data["signal"].shift(1).fillna(0)
* test_data["asset_return"]
)
equity_curve = (1 + test_data["strategy_return"]).cumprod()
buy_and_hold_curve = (1 + test_data["asset_return"].fillna(0)).cumprod()
running_peak = equity_curve.cummax()
drawdown = (equity_curve / running_peak) - 1
strategy_return = equity_curve.iloc[-1] - 1
buy_and_hold_return = buy_and_hold_curve.iloc[-1] - 1
maximum_drawdown = drawdown.min()
print(f"\nSymbol: {SYMBOL}")
print(f"Test observations: {len(test_data)}")
print(f"Model strategy return: {strategy_return:.2%}")
print(f"Buy-and-hold return: {buy_and_hold_return:.2%}")
print(f"Maximum drawdown: {maximum_drawdown:.2%}")
# -----------------------------
# Retrain on all known labelled data
# -----------------------------
model.fit(
labelled_data[FEATURES],
labelled_data["target"].astype(int),
)
latest_features = feature_data[FEATURES].tail(1)
probability_up = model.predict_proba(latest_features)[0, 1]
print(f"Latest probability of an upward move: {probability_up:.2%}")
# -----------------------------
# Optional paper execution
# -----------------------------
if RUN_PAPER_ORDER:
trading_client = TradingClient(
API_KEY,
SECRET_KEY,
paper=True,
)
positions = trading_client.get_all_positions()
open_symbols = {position.symbol for position in positions}
# Buy only when the model has at least 55% estimated probability.
if probability_up >= 0.55 and SYMBOL not in open_symbols:
order = MarketOrderRequest(
symbol=SYMBOL,
notional=POSITION_VALUE,
side=OrderSide.BUY,
time_in_force=TimeInForce.DAY,
)
result = trading_client.submit_order(order_data=order)
print(f"Paper buy submitted: {result.id}")
# Close the paper position when confidence falls to 45% or lower.
elif probability_up <= 0.45 and SYMBOL in open_symbols:
result = trading_client.close_position(SYMBOL)
print(f"Paper position closed: {result}")
else:
print("No paper order submitted.")
The Alpaca SDK provides the data and trading classes used in the script, including StockHistoricalDataClient, StockBarsRequest, TimeFrame.Day, TradingClient and MarketOrderRequest. The paper=True setting keeps order submission in the paper environment.
Step 4: Run the Backtest
Run:
python bot.py
The script prints:
- The symbol being tested
- The number of out-of-sample observations
- The model strategy return
- The buy-and-hold return
- The maximum drawdown
- The latest model probability
A backtest result does not show that the bot will make money. The model may fit one historical period too closely, or its results may change when market conditions change.
The chronological split matters. If older and newer observations are mixed at random, information from a later market period can influence the training set.
Step 5: Enable Paper Trading Carefully
Only change:
RUN_PAPER_ORDER = False
to:
RUN_PAPER_ORDER = True
after checking that:
- The API keys are paper keys.
TradingClientstill usespaper=True.- The backtest uses a chronological split.
- The position size is deliberately small.
- You understand when the bot buys and exits.
- You have checked the Alpaca paper dashboard after each test order.
- You have considered what happens when the script runs repeatedly.
The sample checks whether a position is already open, but it does not track pending orders. Add that safeguard before scheduling the script to run repeatedly.
Alpaca states that market orders are submitted at the best available price, but the execution price is not guaranteed. Its paper environment also does not model several live-market effects, including market impact, latency-related slippage, queue position and dividends.
How to Improve the Free Bot
The starter bot is intentionally limited. Improve it in this order.
Add Realistic Trading Costs
Subtract estimated spreads, commissions where applicable, slippage and other transaction costs from the backtest. A strategy that looks profitable before costs may lose money after execution expenses.
Use Walk-Forward Testing
Instead of training once on 80% of the data, retrain at set points using only information available at that time. This gives a closer approximation of how the model would operate in live conditions.
Test Several Market Regimes
Test the strategy during rising, falling and highly volatile markets. A model that works only during one bullish period may fail during a different period.
Add Risk Controls
Useful controls include:
- A maximum dollar position size
- A maximum number of trades per day
- A maximum daily loss
- A kill switch
- A check for missing data
- A check that the market is open
- Logging for every prediction and order
- Alerts when an order is rejected
Compare Against Simple Baselines
Compare the model with:
- Buy and hold
- A moving-average strategy
- A random-entry strategy
- Holding cash
Machine learning only earns its place if it improves a defined objective after costs and risk are included.
What "AI Trading Bot for Free" Does Not Mean
A free bot means you can build and test the software without paying for an expensive trading platform or using live capital. It does not mean the predictions are reliable or that the strategy can make money without risk.
The U.S. Securities and Exchange Commission, FINRA and NASAA warn that AI-generated investment information can be inaccurate, incomplete or misleading. FINRA also warns that market conditions missing from a model's training data can produce unreliable predictions and unwanted trades.
Be cautious with any service claiming that its AI trading system cannot lose or can produce high returns with little risk. The SEC identifies guaranteed returns, pressure to act quickly and vague AI claims as warning signs of investment fraud.
Bottom Line
This setup is useful for learning how market data, feature engineering, model testing and order submission fit together. Keep it in paper trading until the backtest includes trading costs, the strategy has been tested across different periods, and the script has safeguards for repeated runs and rejected orders.
Treat the model as an experimental decision tool, not a stock-picking system with guaranteed results.