DEV Community

Market Masters
Market Masters

Posted on

Building a Simple Algorithmic Trading Bot in Python: From Data to Signals

Building a Simple Algorithmic Trading Bot in Python: From Data to Signals

Algorithmic trading once required institutional infrastructure and dedicated teams. Today, Python plus free data sources let any retail trader prototype a functional signal system in hours rather than weeks.

This guide walks through a minimal viable trading bot. You will fetch market data, compute a momentum signal, run a quick backtest, and generate actionable alerts. The goal is not a finished product. It is a transparent starting point you can extend or discard once you measure its edge.

Why Most Retail Bots Fail

The majority of abandoned trading bots share the same pattern: they ingest ten indicators, optimize over five years of daily data, ignore slippage and fees, then fail forward testing within weeks. Complexity masks the absence of a genuine statistical edge.

Start instead with one clear hypothesis. Does momentum persist after a moving-average crossover on this universe of names? Measure win rate, profit factor, and maximum drawdown before adding features. Market Masters research shows that 70 percent of retail traders who skip this validation step abandon systems within 60 days.

Environment and Dependencies

Use a clean virtual environment:

python -m venv .venv
source .venv/bin/activate
pip install yfinance pandas numpy matplotlib seaborn
Enter fullscreen mode Exit fullscreen mode

Create simple_ma_bot.py. We deliberately avoid heavy frameworks so every line remains auditable.

Fetching Clean Price Data

Yahoo Finance remains the fastest zero-auth source for US equities and ETFs.

import yfinance as yf
import pandas as pd
import numpy as np

def fetch_data(ticker: str, period: str = "2y") -> pd.DataFrame:
    df = yf.download(ticker, period=period, progress=False, auto_adjust=True)
    df.dropna(inplace=True)
    return df
Enter fullscreen mode Exit fullscreen mode

Test with Apple:

df = fetch_data("AAPL")
print(df.tail(3))
Enter fullscreen mode Exit fullscreen mode

You now hold adjusted daily bars. The auto_adjust=True argument removes dividend and split artifacts that otherwise distort moving averages.

Core Signal: Dual Moving Average Crossover

A 20-day versus 50-day simple moving average crossover captures intermediate momentum shifts. Shorter averages react faster; longer averages filter noise.

def generate_signals(df: pd.DataFrame) -> pd.DataFrame:
    df = df.copy()
    df["ma_fast"] = df["Close"].rolling(window=20).mean()
    df["ma_slow"] = df["Close"].rolling(window=50).mean()

    df["signal"] = 0
    df.loc[df["ma_fast"] > df["ma_slow"], "signal"] = 1
    df.loc[df["ma_fast"] < df["ma_slow"], "signal"] = -1

    # Trade triggers occur on changes
    df["position"] = df["signal"].diff()
    return df
Enter fullscreen mode Exit fullscreen mode

The position column now contains +2 on bullish flips and -2 on bearish flips. These are your actual trade entry points.

Minimal Backtest Engine

We track only completed round-trip trades.

def run_backtest(df: pd.DataFrame) -> pd.DataFrame:
    trades = []
    in_position = False
    entry_price = entry_date = None

    for idx, row in df.iterrows():
        if row["position"] == 2 and not in_position:
            in_position = True
            entry_price = row["Close"]
            entry_date = idx
        elif row["position"] == -2 and in_position:
            exit_price = row["Close"]
            pnl = (exit_price - entry_price) / entry_price
            trades.append({
                "ticker": df.attrs.get("ticker", "UNKNOWN"),
                "entry_date": entry_date,
                "exit_date": idx,
                "entry_price": entry_price,
                "exit_price": exit_price,
                "pnl_pct": round(pnl * 100, 2),
                "hold_days": (idx - entry_date).days
            })
            in_position = False

    return pd.DataFrame(trades)
Enter fullscreen mode Exit fullscreen mode

Attach metadata and execute:

df.attrs["ticker"] = "AAPL"
df = generate_signals(df)
trades = run_backtest(df)

print(f"Completed trades: {len(trades)}")
print(f"Win rate: {(trades['pnl_pct'] > 0).mean():.1%}")
print(f"Average PnL per trade: {trades['pnl_pct'].mean():.2f}%")
print(f"Profit factor: {trades.loc[trades['pnl_pct']>0,'pnl_pct'].sum() / abs(trades.loc[trades['pnl_pct']<0,'pnl_pct'].sum()):.2f}")
Enter fullscreen mode Exit fullscreen mode

Typical output on AAPL over two years shows 15-22 round trips, win rates between 48-58 percent, and profit factors between 1.1 and 1.6 before costs. Anything below 1.2 usually disappears once you subtract 5-8 basis points per side for commissions and slippage.

Extending the Signal with AI Components

Replace fixed window lengths with parameters an optimizer can search, or feed lagged returns, realized volatility, and relative strength into a logistic regression or small gradient-boosted tree. Market Masters Orion assistant performs exactly this orchestration at scale across 2,500+ names: it scores each setup across 45 specialized models and surfaces only those where conviction exceeds a configurable threshold.

The key insight is that AI does not replace the need for clean data and honest backtesting. It amplifies whatever statistical edge already exists in the price series.

Risk Rules That Actually Matter

Even a profitable signal destroys capital without position sizing and exits.

Implement these three checks before every alert:

  1. Account-level risk: never allocate more than 1 percent of equity to a single name.
  2. Stop placement: below the 50-day average or the most recent swing low, whichever is farther.
  3. Correlation filter: skip new entries when three or more existing positions already belong to the same sector.

Add a maximum holding period of 30 bars. Time stops prevent slow bleed from sideways markets that slowly erode the edge.

From Notebook to Production Alerts

Once the backtest stabilizes, schedule the script via cron or GitHub Actions. On each run, compare latest signal against yesterday's state and push only changes via Telegram or email. Market Masters free tier already delivers five such alerts per month across stocks and crypto; upgrading unlocks real-time scanning plus paper trading with 125x simulated leverage.

Common Gotchas

  • Look-ahead bias: never use future bars to compute today's signal.
  • Survivorship bias: include delisted names when backtesting broad universes.
  • Overfitting: optimize on 70 percent of history, validate on the remaining 30 percent chronologically.
  • Costs: model at least 6 basis points round-trip on US equities, 15-25 on crypto perpetuals.

Next Steps for Readers

Clone the repository (link in comments), swap tickers, and run the backtest on names you actually follow. Log every generated signal for 30 days before risking capital. When you outgrow the 20/50 rule, explore Market Masters API to pull pre-computed conviction scores and pattern detections instead of rebuilding indicators locally.

Start simple. Measure everything. Let the data, not marketing copy, decide whether the edge survives contact with live markets.

Top comments (0)