{"slug": "how-to-build-an-autonomous-trading-agent-with-python", "title": "How to Build an Autonomous Trading Agent with Python", "summary": "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.", "body_md": "**Build an AI‑powered crypto‑price‑prediction service that logs every prediction on‑chain**\n\nYou’ll end up with:\n\n| Component | Tech Stack | What it does |\n|---|---|---|\n| Data ingestion | Python + CoinGecko API | Pulls historic OHLCV data |\n| Model | TensorFlow (LSTM) | Trains a short‑term price‑forecast model |\n| API | FastAPI | Serves `GET /predict` and `POST /log` endpoints |\n| Smart contract | Solidity (Ethereum) | Stores each prediction (timestamp, price) in an immutable ledger |\n| Front‑end | React + Vite | Shows the latest prediction & lets users submit their own |\n| Deployment | Docker Compose (local) → AWS ECS / GCP Cloud Run (optional) | One‑click spin‑up of the whole stack |\n\n| Category | Required |\n|---|---|\nProgramming |\nPython ≥ 3.10, JavaScript/TypeScript, basic Solidity |\nTools |\nGit, Docker ≥ 20.10, Node ≥ 18, npm ≥ 9, VS Code (or any IDE) |\nAccounts |\nFree Infura or Alchemy project (Ethereum RPC), Etherscan API key (optional) |\nCrypto |\nTestnet ETH (e.g., Sepolia) – get via a faucet |\nKnowledge |\n1‑line basics of REST, LSTM, smart contracts, and Docker |\n\nTip: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.\n\n```\ncrypto‑ai‑predictor/\n├─ backend/                # FastAPI + ML model\n│   ├─ app/\n│   │   ├─ main.py\n│   │   ├─ model.py\n│   │   └─ utils.py\n│   ├─ Dockerfile\n│   └─ requirements.txt\n├─ contracts/              # Solidity contract + deployment scripts\n│   ├─ PredictionLogger.sol\n│   └─ scripts/\n│       └─ deploy.ts\n├─ frontend/               # React UI\n│   ├─ src/\n│   │   ├─ App.tsx\n│   │   └─ api.ts\n│   └─ Dockerfile\n├─ docker-compose.yml\n└─ README.md\n```\n\nWe’ll fill each folder step‑by‑step.\n\n```\ngit clone https://github.com/yourname/crypto-ai-predictor.git\ncd crypto-ai-predictor\n```\n\nCreate a Python virtual environment (optional – Docker will handle it later):\n\n```\npython -m venv .venv\nsource .venv/bin/activate   # Windows: .venv\\Scripts\\activate\n```\n\nCreate `backend/requirements.txt`\n\n:\n\n```\nfastapi==0.110.0\nuvicorn[standard]==0.29.0\npandas==2.2.2\nnumpy==1.26.4\nscikit-learn==1.5.0\ntensorflow==2.16.1\npython-dotenv==1.0.1\nrequests==2.32.3\nweb3==6.19.0\npip install -r backend/requirements.txt\n```\n\nCreate `backend/app/utils.py`\n\n:\n\n``` python\nimport os\nimport requests\nimport pandas as pd\nfrom datetime import datetime, timedelta\n\nCOINGECKO_API = \"https://api.coingecko.com/api/v3\"\nDEFAULT_COIN = \"bitcoin\"\nDEFAULT_VS_CURRENCY = \"usd\"\n\ndef fetch_ohlcv(days: int = 90, coin: str = DEFAULT_COIN) -> pd.DataFrame:\n    \"\"\"\n    Returns a DataFrame with columns:\n    ['timestamp','open','high','low','close','volume']\n    \"\"\"\n    # CoinGecko returns daily candles for the last N days\n    url = f\"{COINGECKO_API}/coins/{coin}/ohlc\"\n    params = {\"vs_currency\": DEFAULT_VS_CURRENCY, \"days\": days}\n    resp = requests.get(url, params=params)\n    resp.raise_for_status()\n    data = resp.json()  # [[unix, open, high, low, close], ...]\n\n    df = pd.DataFrame(data, columns=[\"timestamp\", \"open\", \"high\", \"low\", \"close\"])\n    df[\"timestamp\"] = pd.to_datetime(df[\"timestamp\"], unit=\"ms\")\n    # Estimate volume (CoinGecko does not give it in the OHLC endpoint)\n    # We'll fetch market data and calculate a proxy\n    vol_url = f\"{COINGECKO_API}/coins/{coin}/market_chart\"\n    vol_params = {\"vs_currency\": DEFAULT_VS_CURRENCY, \"days\": days}\n    vol_resp = requests.get(vol_url, params=vol_params)\n    vol_resp.raise_for_status()\n    vol_data = vol_resp.json()[\"total_volumes\"]  # [[unix, volume], ...]\n    vol_df = pd.DataFrame(vol_data, columns=[\"timestamp\", \"volume\"])\n    vol_df[\"timestamp\"] = pd.to_datetime(vol_df[\"timestamp\"], unit=\"ms\")\n    df = df.merge(vol_df, on=\"timestamp\")\n    df.set_index(\"timestamp\", inplace=True)\n    return df\n```\n\nExplanation– 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.\n\nCreate `backend/app/model.py`\n\n:\n\n``` python\nimport numpy as np\nimport pandas as pd\nimport tensorflow as tf\nfrom tensorflow.keras import layers, models, callbacks\nfrom sklearn.preprocessing import MinMaxScaler\nfrom .utils import fetch_ohlcv\n\n# ----------------------------------------------------------------------\n# 1️⃣  Data preprocessing\n# ----------------------------------------------------------------------\ndef prepare_dataset(df: pd.DataFrame, lookback: int = 30) -> tuple:\n    \"\"\"\n    Returns (X, y) where:\n      X shape = (samples, lookback, features)\n      y shape = (samples, 1)   -> next day's closing price\n    \"\"\"\n    scaler = MinMaxScaler()\n    scaled = scaler.fit_transform(df)\n\n    X, y = [], []\n    for i in range(len(scaled) - lookback):\n        X.append(scaled[i : i + lookback])\n        y.append(scaled[i + lookback, 3])          # column 3 = close\n    X = np.array(X)\n    y = np.array(y).reshape(-1, 1)\n    return X, y, scaler\n\n# ----------------------------------------------------------------------\n# 2️⃣  Model definition\n# ----------------------------------------------------------------------\ndef build_lstm(input_shape):\n    model = models.Sequential([\n        layers.LSTM(64, activation='tanh', input_shape=input_shape),\n        layers.Dense(32, activation='relu'),\n        layers.Dense(1)  # predict scaled close price\n    ])\n    model.compile(optimizer='adam', loss='mse')\n    return model\n\n# ----------------------------------------------------------------------\n# 3️⃣  Training routine (called from main)\n# ----------------------------------------------------------------------\ndef train_and_save(model_path: str = \"model.h5\", lookback: int = 30):\n    df = fetch_ohlcv(days=180)               # 6‑months of data for better generalisation\n    X, y, scaler = prepare_dataset(df, lookback)\n\n    model = build_lstm(input_shape=X.shape[1:])\n    es = callbacks.EarlyStopping(patience=10, restore_best_weights=True)\n    model.fit(X, y, epochs=200, batch_size=16, validation_split=0.2, callbacks=[es])\n\n    # Save both model and scaler (pickle)\n    model.save(model_path)\n    import joblib, pathlib\n    pathlib.Path(\"scaler.pkl\").write_bytes(joblib.dumps(scaler))\n    print(f\"✅ Model saved to {model_path}\")\n```\n\nWhy LSTM?It captures temporal dependencies in price series with few parameters—perfect for a demo.\n\nCreate `backend/app/main.py`\n\n:\n\n``` python\nimport os\nimport json\nimport numpy as np\nimport pandas as pd\nimport joblib\nfrom fastapi import FastAPI, HTTPException\nfrom pydantic import BaseModel\nfrom tensorflow.keras.models import load_model\nfrom .utils import fetch_ohlcv\nfrom .model import prepare_dataset\n\napp = FastAPI(title=\"Crypto AI Predictor\", version=\"0.1.0\")\n\n# --------------------------------------------------------------\n# Load model & scaler at startup\n# --------------------------------------------------------------\nMODEL_PATH = os.getenv(\"MODEL_PATH\", \"model.h5\")\nSCALER_PATH = os.getenv(\"SCALER_PATH\", \"scaler.pkl\")\nmodel = load_model(MODEL_PATH)\nscaler = joblib.load(SCALER_PATH)\n\nLOOKBACK = 30   # same as in training\n\nclass PredictResponse(BaseModel):\n    timestamp: str\n    predicted_price: float\n    confidence: float | None = None   # placeholder for future extension\n\n# --------------------------------------------------------------\n# Helper: turn latest OHLCV into a prediction tensor\n# --------------------------------------------------------------\ndef get_latest_tensor() -> np.ndarray:\n    df = fetch_ohlcv(days=LOOKBACK + 1)   # we need LOOKBACK rows\n    # Keep same column order as during training\n    df = df[[\"open\", \"high\", \"low\", \"close\", \"volume\"]]\n    scaled = scaler.transform(df)\n    tensor = scaled[-LOOKBACK:]            # shape (lookback, 5)\n    return tensor.reshape((1, LOOKBACK, 5))\n\n# --------------------------------------------------------------\n# Public endpoint – return next‑day price prediction\n# --------------------------------------------------------------\n@app.get(\"/predict\", response_model=PredictResponse)\ndef predict():\n    try:\n        tensor = get_latest_tensor()\n        pred_scaled = model.predict(tensor)[0][0]          # scalar\n        # Inverse‑scale only the *close* column (index 3)\n        dummy = np.zeros((1, scaler.n_features_in_))\n        dummy[0, 3] = pred_scaled\n        pred_price = scaler.inverse_transform(dummy)[0, 3]\n        ts = pd.Timestamp.utcnow().isoformat()\n        return PredictResponse(timestamp=ts, predicted_price=round(float(pred_price), 2))\n    except Exception as e:\n        raise HTTPException(status_code=500, detail=str(e))\n\n# --------------------------------------------------------------\n# Optional: endpoint to trigger a re‑train (protected in prod)\n# --------------------------------------------------------------\n@app.post(\"/train\")\ndef train():\n    from .model import train_and_save\n    train_and_save(model_path=MODEL_PATH)\n    # reload\n    global model, scaler\n    model = load_model(MODEL_PATH)\n    scaler = joblib.load(SCALER_PATH)\n    return {\"status\": \"retrained\"}\n```\n\nExplanation–\n\n`GET /predict`\n\ngrabs the most recent 30 days, scales them, feeds to the LSTM, then de‑scales the predicted close price.`POST /train`\n\nis a convenience for local testing; in production you’d protect it with an API key or CI pipeline.\n\nCreate `backend/Dockerfile`\n\n:\n\n```\n# syntax = docker/dockerfile:1.4\nFROM python:3.12-slim AS builder\n\nWORKDIR /app\nCOPY backend/requirements.txt .\nRUN pip install --upgrade pip && \\\n    pip install --no-cache-dir -r requirements.txt\n\n# ---- Runtime image ----\nFROM python:3.12-slim\nWORKDIR /app\nCOPY --from=builder /usr/local/lib/python3.12/site-packages /usr/local/lib/python3.12/site-packages\nCOPY backend/app ./app\n\n# Model artefacts (you can also mount them as volumes)\nCOPY backend/model.h5 .\nCOPY backend/scaler.pkl .\n\nEXPOSE 8000\nCMD [\"uvicorn\", \"app.main:app\", \"--host\", \"0.0.0.0\", \"--port\", \"8000\"]\n```\n\nWhy multi‑stage?Keeps the final image tiny (~70 MB) – perfect for serverless containers.\n\nCreate `contracts/PredictionLogger.sol`\n\n:\n\n```\n// SPDX-License-Identifier: MIT\npragma solidity ^0.8.24;\n\ncontract PredictionLogger {\n    struct Prediction {\n        uint256 timestamp;   // block timestamp when logged\n        uint256 price;       // price * 1e2 (e.g., $27,345.12 → 2734512)\n        address reporter;   // who logged it\n    }\n\n    Prediction[] public predictions;\n\n    event PredictionLogged(uint256 indexed idx, uint256 timestamp, uint256 price, address reporter);\n\n    /// @notice Store a new prediction. Caller pays only gas.\n    /// @param price price in cents (2 decimals) to avoid floating points.\n    function logPrediction(uint256 price) external {\n        predictions.push(Prediction({\n            timestamp: block.timestamp,\n            price: price,\n            reporter: msg.sender\n        }));\n        emit PredictionLogged(predictions.length - 1, block.timestamp, price, msg.sender);\n    }\n\n    /// @notice Get total number of predictions logged.\n    function count() external view returns (uint256) {\n        return predictions.length;\n    }\n\n    /// @notice Retrieve a prediction by index.\n    function get(uint256 idx) external view returns (Prediction memory) {\n        require(idx < predictions.length, \"out of range\");\n        return predictions[idx];\n    }\n}\n```\n\nDesign notes\n\n- We store price as an integer with 2 decimal places (\n`uint256 price`\n\n). Solidity has no floating‑point numbers.`logPrediction`\n\nispayable‑free – anyone can call it, but you could add an`onlyOwner`\n\nor a small fee later.\n\n```\ncd contracts\nnpm init -y\nnpm i --save-dev hardhat @nomicfoundation/hardhat-toolbox ethers dotenv\nnpx hardhat\n# Choose \"Create a basic sample project\"\n```\n\nAdd `.env`\n\nin `contracts/`\n\n:\n\n```\nSEPOLIA_RPC_URL=https://sepolia.infura.io/v3/YOUR_INFURA_PROJECT_ID\nPRIVATE_KEY=0xYOUR_PRIVATE_KEY   # account with testnet ETH\n```\n\nUpdate `hardhat.config.ts`\n\n:\n\n``` js\nimport { config as dotenvConfig } from \"dotenv\";\nimport { HardhatUserConfig } from \"hardhat/types\";\ndotenvConfig();\n\nconst config: HardhatUserConfig = {\n  solidity: \"0.8.24\",\n  networks: {\n    sepolia: {\n      url: process.env.SEPOLIA_RPC_URL || \"\",\n      accounts: process.env.PRIVATE_KEY ? [process.env.PRIVATE_KEY] : [],\n    },\n  },\n};\n\nexport default config;\n```\n\nCreate `scripts/deploy.ts`\n\n:\n\n``` js\nimport { ethers } from \"hardhat\";\n\nasync function main() {\n  const PredictionLogger = await ethers.getContractFactory(\"PredictionLogger\");\n  const logger = await PredictionLogger.deploy();\n  await logger.waitForDeployment();\n\n  console.log(\"✅ PredictionLogger deployed to:\", await logger.getAddress());\n}\n\nmain().catch((error) => {\n  console.error(error);\n  process.exitCode = 1;\n});\n```\n\nDeploy to Sepolia:\n\n```\nnpx hardhat run scripts/deploy.ts --network sepolia\n# copy the printed address – you’ll need it in the backend\n```\n\nAfter compilation, the ABI is in `artifacts/contracts/PredictionLogger.sol/PredictionLogger.json`\n\n. Copy the `abi`\n\narray into the backend folder:\n\n```\nmkdir -p backend/app/abi\ncp artifacts/contracts/PredictionLogger.sol/PredictionLogger.json backend/app/abi/\n```\n\nRename it to `PredictionLogger_abi.json`\n\nfor clarity.\n\nAdd `web3`\n\nto the backend (already in `requirements.txt`\n\n).\n\nCreate `backend/app/blockchain.py`\n\n:\n\n``` python\npython\nimport os\nfrom web3 import Web3\nimport json\nfrom pathlib import Path\n\n# Load env variables (you can use python-dotenv)\nINFURA_URL = os.getenv(\"INFURA_URL\")  # e.g. https://sepolia.infura.io/v3/xxxx\nPRIVATE_KEY = os.getenv(\"PRIVATE_KEY\")  # for signing txs (test account)\n\nw3 = Web3(Web3.HTTPProvider(INFURA_URL))\nassert w3.is_connected(), \"❌ Can't connect to Ethereum node\"\n\n# Load contract ABI & address\nABI_PATH = Path(__file__).parent / \"abi\" / \"PredictionLogger_abi.json\"\nwith open(ABI_PATH) as f:\n    abi = json.load(f)[\"abi\"]\n\nCONTRACT_ADDRESS = os.getenv(\"CONTRACT_ADDRESS\")  # set after deployment\ncontract = w3.eth.contract(address=CONTRACT_ADDRESS, abi=abi)\n\ndef log_prediction_onchain(price_usd: float) -> str:\n    \"\"\"\n    Sends a transaction to store `price_usd` (2 decimal precision) on‑chain.\n    Returns the transaction hash.\n    \"\"\"\n    # Convert to integer cents\n    price_cents = int(round(price_usd * 100))\n\n    # Build transaction\n    nonce = w3.eth.get_transaction_count(w3.eth.account.from_key(PRIVATE_KEY).address)\n    tx = contract.functions.logPrediction(price_cents).build_transaction({\n        \"chainId\": w3.eth.chain_id,\n        \"gas\": 200_000,\n        \"gasPrice\": w3.to_wei(\"5\", \"gwei\"),\n\n#coding #tutorial #web3 #AI\n```\n\n", "url": "https://wpnews.pro/news/how-to-build-an-autonomous-trading-agent-with-python", "canonical_source": "https://dev.to/naren_karthi/how-to-build-an-autonomous-trading-agent-with-python-3fll", "published_at": "2026-08-29 12:35:27+00:00", "updated_at": "2026-08-29 12:48:49.613110+00:00", "lang": "en", "topics": ["artificial-intelligence", "machine-learning", "large-language-models", "developer-tools"], "entities": ["Python", "TensorFlow", "FastAPI", "Solidity", "Ethereum", "React", "Docker", "CoinGecko"], "alternates": {"html": "https://wpnews.pro/news/how-to-build-an-autonomous-trading-agent-with-python", "markdown": "https://wpnews.pro/news/how-to-build-an-autonomous-trading-agent-with-python.md", "text": "https://wpnews.pro/news/how-to-build-an-autonomous-trading-agent-with-python.txt", "jsonld": "https://wpnews.pro/news/how-to-build-an-autonomous-trading-agent-with-python.jsonld"}}