cd /news/artificial-intelligence/how-to-build-an-autonomous-trading-a… · home topics artificial-intelligence article
[ARTICLE · art-115106] src=dev.to ↗ pub= topic=artificial-intelligence verified=true sentiment=· neutral

How to Build an Autonomous Trading Agent with Python

A developer has published a tutorial on building an autonomous trading agent with Python, which uses TensorFlow LSTM models to predict crypto prices and logs predictions on-chain via a Solidity smart contract. The project includes a FastAPI backend, a React frontend, and Docker deployment, and is designed to be accessible to developers with basic programming knowledge.

read8 min views1 publishedAug 29, 2026

Build an AI‑powered crypto‑price‑prediction service that logs every prediction on‑chain

You’ll end up with:

Component Tech Stack What it does
Data ingestion Python + CoinGecko API Pulls historic OHLCV data
Model TensorFlow (LSTM) Trains a short‑term price‑forecast model
API FastAPI Serves GET /predict and POST /log endpoints
Smart contract Solidity (Ethereum) Stores each prediction (timestamp, price) in an immutable ledger
Front‑end React + Vite Shows the latest prediction & lets users submit their own
Deployment Docker Compose (local) → AWS ECS / GCP Cloud Run (optional) One‑click spin‑up of the whole stack
Category Required
Programming
Python ≥ 3.10, JavaScript/TypeScript, basic Solidity
Tools
Git, Docker ≥ 20.10, Node ≥ 18, npm ≥ 9, VS Code (or any IDE)
Accounts
Free Infura or Alchemy project (Ethereum RPC), Etherscan API key (optional)
Crypto
Testnet ETH (e.g., Sepolia) – get via a faucet
Knowledge
1‑line basics of REST, LSTM, smart contracts, and Docker

Tip:If you’re new to any of these, skim the official “Getting Started” docs first – the tutorial works even if you only know the basics.

crypto‑ai‑predictor/
├─ backend/                # FastAPI + ML model
│   ├─ app/
│   │   ├─ main.py
│   │   ├─ model.py
│   │   └─ utils.py
│   ├─ Dockerfile
│   └─ requirements.txt
├─ contracts/              # Solidity contract + deployment scripts
│   ├─ PredictionLogger.sol
│   └─ scripts/
│       └─ deploy.ts
├─ frontend/               # React UI
│   ├─ src/
│   │   ├─ App.tsx
│   │   └─ api.ts
│   └─ Dockerfile
├─ docker-compose.yml
└─ README.md

We’ll fill each folder step‑by‑step.

git clone https://github.com/yourname/crypto-ai-predictor.git
cd crypto-ai-predictor

Create a Python virtual environment (optional – Docker will handle it later):

python -m venv .venv
source .venv/bin/activate   # Windows: .venv\Scripts\activate

Create backend/requirements.txt

:

fastapi==0.110.0
uvicorn[standard]==0.29.0
pandas==2.2.2
numpy==1.26.4
scikit-learn==1.5.0
tensorflow==2.16.1
python-dotenv==1.0.1
requests==2.32.3
web3==6.19.0
pip install -r backend/requirements.txt

Create backend/app/utils.py

:

import os
import requests
import pandas as pd
from datetime import datetime, timedelta

COINGECKO_API = "https://api.coingecko.com/api/v3"
DEFAULT_COIN = "bitcoin"
DEFAULT_VS_CURRENCY = "usd"

def fetch_ohlcv(days: int = 90, coin: str = DEFAULT_COIN) -> pd.DataFrame:
    """
    Returns a DataFrame with columns:
    ['timestamp','open','high','low','close','volume']
    """
    url = f"{COINGECKO_API}/coins/{coin}/ohlc"
    params = {"vs_currency": DEFAULT_VS_CURRENCY, "days": days}
    resp = requests.get(url, params=params)
    resp.raise_for_status()
    data = resp.json()  # [[unix, open, high, low, close], ...]

    df = pd.DataFrame(data, columns=["timestamp", "open", "high", "low", "close"])
    df["timestamp"] = pd.to_datetime(df["timestamp"], unit="ms")
    vol_url = f"{COINGECKO_API}/coins/{coin}/market_chart"
    vol_params = {"vs_currency": DEFAULT_VS_CURRENCY, "days": days}
    vol_resp = requests.get(vol_url, params=vol_params)
    vol_resp.raise_for_status()
    vol_data = vol_resp.json()["total_volumes"]  # [[unix, volume], ...]
    vol_df = pd.DataFrame(vol_data, columns=["timestamp", "volume"])
    vol_df["timestamp"] = pd.to_datetime(vol_df["timestamp"], unit="ms")
    df = df.merge(vol_df, on="timestamp")
    df.set_index("timestamp", inplace=True)
    return df

Explanation– CoinGecko’s free tier gives us 90‑day daily candles without any API key. The function merges volume data to give a full OHLCV dataset.

Create backend/app/model.py

:

import numpy as np
import pandas as pd
import tensorflow as tf
from tensorflow.keras import layers, models, callbacks
from sklearn.preprocessing import MinMaxScaler
from .utils import fetch_ohlcv

def prepare_dataset(df: pd.DataFrame, lookback: int = 30) -> tuple:
    """
    Returns (X, y) where:
      X shape = (samples, lookback, features)
      y shape = (samples, 1)   -> next day's closing price
    """
    scaler = MinMaxScaler()
    scaled = scaler.fit_transform(df)

    X, y = [], []
    for i in range(len(scaled) - lookback):
        X.append(scaled[i : i + lookback])
        y.append(scaled[i + lookback, 3])          # column 3 = close
    X = np.array(X)
    y = np.array(y).reshape(-1, 1)
    return X, y, scaler

def build_lstm(input_shape):
    model = models.Sequential([
        layers.LSTM(64, activation='tanh', input_shape=input_shape),
        layers.Dense(32, activation='relu'),
        layers.Dense(1)  # predict scaled close price
    ])
    model.compile(optimizer='adam', loss='mse')
    return model

def train_and_save(model_path: str = "model.h5", lookback: int = 30):
    df = fetch_ohlcv(days=180)               # 6‑months of data for better generalisation
    X, y, scaler = prepare_dataset(df, lookback)

    model = build_lstm(input_shape=X.shape[1:])
    es = callbacks.EarlyStopping(patience=10, restore_best_weights=True)
    model.fit(X, y, epochs=200, batch_size=16, validation_split=0.2, callbacks=[es])

    model.save(model_path)
    import joblib, pathlib
    pathlib.Path("scaler.pkl").write_bytes(joblib.dumps(scaler))
    print(f"✅ Model saved to {model_path}")

Why LSTM?It captures temporal dependencies in price series with few parameters—perfect for a demo.

Create backend/app/main.py

:

import os
import json
import numpy as np
import pandas as pd
import joblib
from fastapi import FastAPI, HTTPException
from pydantic import BaseModel
from tensorflow.keras.models import load_model
from .utils import fetch_ohlcv
from .model import prepare_dataset

app = FastAPI(title="Crypto AI Predictor", version="0.1.0")

MODEL_PATH = os.getenv("MODEL_PATH", "model.h5")
SCALER_PATH = os.getenv("SCALER_PATH", "scaler.pkl")
model = load_model(MODEL_PATH)
scaler = joblib.load(SCALER_PATH)

LOOKBACK = 30   # same as in training

class PredictResponse(BaseModel):
    timestamp: str
    predicted_price: float
    confidence: float | None = None   # placeholder for future extension

def get_latest_tensor() -> np.ndarray:
    df = fetch_ohlcv(days=LOOKBACK + 1)   # we need LOOKBACK rows
    df = df[["open", "high", "low", "close", "volume"]]
    scaled = scaler.transform(df)
    tensor = scaled[-LOOKBACK:]            # shape (lookback, 5)
    return tensor.reshape((1, LOOKBACK, 5))

@app.get("/predict", response_model=PredictResponse)
def predict():
    try:
        tensor = get_latest_tensor()
        pred_scaled = model.predict(tensor)[0][0]          # scalar
        dummy = np.zeros((1, scaler.n_features_in_))
        dummy[0, 3] = pred_scaled
        pred_price = scaler.inverse_transform(dummy)[0, 3]
        ts = pd.Timestamp.utcnow().isoformat()
        return PredictResponse(timestamp=ts, predicted_price=round(float(pred_price), 2))
    except Exception as e:
        raise HTTPException(status_code=500, detail=str(e))

@app.post("/train")
def train():
    from .model import train_and_save
    train_and_save(model_path=MODEL_PATH)
    global model, scaler
    model = load_model(MODEL_PATH)
    scaler = joblib.load(SCALER_PATH)
    return {"status": "retrained"}

Explanation–

GET /predict

grabs the most recent 30 days, scales them, feeds to the LSTM, then de‑scales the predicted close price.POST /train

is a convenience for local testing; in production you’d protect it with an API key or CI pipeline.

Create backend/Dockerfile

:

FROM python:3.12-slim AS builder

WORKDIR /app
COPY backend/requirements.txt .
RUN pip install --upgrade pip && \
    pip install --no-cache-dir -r requirements.txt

FROM python:3.12-slim
WORKDIR /app
COPY --from=builder /usr/local/lib/python3.12/site-packages /usr/local/lib/python3.12/site-packages
COPY backend/app ./app

COPY backend/model.h5 .
COPY backend/scaler.pkl .

EXPOSE 8000
CMD ["uvicorn", "app.main:app", "--host", "0.0.0.0", "--port", "8000"]

Why multi‑stage?Keeps the final image tiny (~70 MB) – perfect for serverless containers.

Create contracts/PredictionLogger.sol

:

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.24;

contract PredictionLogger {
    struct Prediction {
        uint256 timestamp;   // block timestamp when logged
        uint256 price;       // price * 1e2 (e.g., $27,345.12 → 2734512)
        address reporter;   // who logged it
    }

    Prediction[] public predictions;

    event PredictionLogged(uint256 indexed idx, uint256 timestamp, uint256 price, address reporter);

    /// @notice Store a new prediction. Caller pays only gas.
    /// @param price price in cents (2 decimals) to avoid floating points.
    function logPrediction(uint256 price) external {
        predictions.push(Prediction({
            timestamp: block.timestamp,
            price: price,
            reporter: msg.sender
        }));
        emit PredictionLogged(predictions.length - 1, block.timestamp, price, msg.sender);
    }

    /// @notice Get total number of predictions logged.
    function count() external view returns (uint256) {
        return predictions.length;
    }

    /// @notice Retrieve a prediction by index.
    function get(uint256 idx) external view returns (Prediction memory) {
        require(idx < predictions.length, "out of range");
        return predictions[idx];
    }
}

Design notes

  • We store price as an integer with 2 decimal places ( uint256 price

). Solidity has no floating‑point numbers.logPrediction

ispayable‑free – anyone can call it, but you could add anonlyOwner

or a small fee later.

cd contracts
npm init -y
npm i --save-dev hardhat @nomicfoundation/hardhat-toolbox ethers dotenv
npx hardhat

Add .env

in contracts/

:

SEPOLIA_RPC_URL=https://sepolia.infura.io/v3/YOUR_INFURA_PROJECT_ID
PRIVATE_KEY=0xYOUR_PRIVATE_KEY   # account with testnet ETH

Update hardhat.config.ts

:

import { config as dotenvConfig } from "dotenv";
import { HardhatUserConfig } from "hardhat/types";
dotenvConfig();

const config: HardhatUserConfig = {
  solidity: "0.8.24",
  networks: {
    sepolia: {
      url: process.env.SEPOLIA_RPC_URL || "",
      accounts: process.env.PRIVATE_KEY ? [process.env.PRIVATE_KEY] : [],
    },
  },
};

export default config;

Create scripts/deploy.ts

:

import { ethers } from "hardhat";

async function main() {
  const PredictionLogger = await ethers.getContractFactory("PredictionLogger");
  const logger = await PredictionLogger.deploy();
  await logger.waitForDeployment();

  console.log("✅ PredictionLogger deployed to:", await logger.getAddress());
}

main().catch((error) => {
  console.error(error);
  process.exitCode = 1;
});

Deploy to Sepolia:

npx hardhat run scripts/deploy.ts --network sepolia

After compilation, the ABI is in artifacts/contracts/PredictionLogger.sol/PredictionLogger.json

. Copy the abi

array into the backend folder:

mkdir -p backend/app/abi
cp artifacts/contracts/PredictionLogger.sol/PredictionLogger.json backend/app/abi/

Rename it to PredictionLogger_abi.json

for clarity.

Add web3

to the backend (already in requirements.txt

).

Create backend/app/blockchain.py

:

python
import os
from web3 import Web3
import json
from pathlib import Path

INFURA_URL = os.getenv("INFURA_URL")  # e.g. https://sepolia.infura.io/v3/xxxx
PRIVATE_KEY = os.getenv("PRIVATE_KEY")  # for signing txs (test account)

w3 = Web3(Web3.HTTPProvider(INFURA_URL))
assert w3.is_connected(), "❌ Can't connect to Ethereum node"

ABI_PATH = Path(__file__).parent / "abi" / "PredictionLogger_abi.json"
with open(ABI_PATH) as f:
    abi = json.load(f)["abi"]

CONTRACT_ADDRESS = os.getenv("CONTRACT_ADDRESS")  # set after deployment
contract = w3.eth.contract(address=CONTRACT_ADDRESS, abi=abi)

def log_prediction_onchain(price_usd: float) -> str:
    """
    Sends a transaction to store `price_usd` (2 decimal precision) on‑chain.
    Returns the transaction hash.
    """
    price_cents = int(round(price_usd * 100))

    nonce = w3.eth.get_transaction_count(w3.eth.account.from_key(PRIVATE_KEY).address)
    tx = contract.functions.logPrediction(price_cents).build_transaction({
        "chainId": w3.eth.chain_id,
        "gas": 200_000,
        "gasPrice": w3.to_wei("5", "gwei"),

#coding #tutorial #web3 #AI
── more in #artificial-intelligence 4 stories · sorted by recency
── more on @python 3 stories trending now
sponsored brought to you by zahid.host 4,200+ EU-deployed projects
reading about agents? ship yours in a single git push.

Run your AI side-project on zahid.host

EU-based hosting, git-push deploys, automatic HTTPS, no cold starts. Free tier with a custom domain — perfect for shipping the agent you just read about.

$git push zahid main
Live at https://your-agent.zahid.host
Get free account → Pricing
from €0/mo · no card required
LIVE [news/how-to-build-an-auto…] indexed:0 read:8min 2026-08-29 ·