cd /news/machine-learning/building-a-self-optimizing-python-tr… · home topics machine-learning article
[ARTICLE · art-34214] src=dev.to ↗ pub= topic=machine-learning verified=true sentiment=· neutral

Building a Self-Optimizing Python Trading Bot with Reinforcement Learning and Binance API

A developer built a self-optimizing Python trading bot using reinforcement learning and the Binance API. The bot uses a custom Gym environment with a PPO agent from Stable-Baselines3 to learn trading strategies from market data. The implementation includes a trading environment with buy, sell, and hold actions, and the agent is trained on normalized price history.

read3 min views1 publishedJun 19, 2026

Algorithmic trading has evolved from simple rule-based systems to sophisticated machine learning models. Reinforcement Learning (RL) offers a paradigm where trading bots can learn optimal strategies through interaction with market data, adapting to changing conditions without explicit programming.

In this guide, we’ll build a self-optimizing trading bot using Python, the Binance API, and RL. We'll cover:

By the end, you’ll have a functional RL-based trading bot that learns from market data and improves over time.

Install the following packages:

pip install python-binance gym numpy pandas torch stable-baselines3
python
   from binance.client import Client

   API_KEY = "your_api_key"
   API_SECRET = "your_api_secret"
   client = Client(API_KEY, API_SECRET)

Security Note: Use environment variables or a secrets manager for production.

RL environments follow the gym.Env

interface:

Create trading_env.py

:

import gym
import numpy as np
from gym import spaces
from binance.client import Client

class TradingEnv(gym.Env):
    def __init__(self, client, symbol="BTCUSDT", window_size=10):
        super(TradingEnv, self).__init__()
        self.client = client
        self.symbol = symbol
        self.window_size = window_size

        self.action_space = spaces.Discrete(3)

        self.observation_space = spaces.Box(
            low=0, high=1, shape=(window_size,), dtype=np.float32
        )

        self.reset()

    def _get_observation(self):
        klines = self.client.get_historical_klines(
            self.symbol, Client.KLINE_INTERVAL_1MINUTE, f"{self.window_size} minutes ago"
        )
        closes = [float(k[4]) for k in klines]
        closes = np.array(closes)

        if self.max_price is None:
            self.max_price = closes.max()
        closes = closes / self.max_price

        return closes

    def reset(self):
        self.balance = 1000  # Starting balance (USD)
        self.position = 0    # Current BTC position
        self.max_price = None
        return self._get_observation()

    def step(self, action):
        current_price = self._get_observation()[-1] * self.max_price
        reward = 0

        if action == 1:  # Buy
            if self.balance > 0:
                self.position = self.balance / current_price
                self.balance = 0
        elif action == 2:  # Sell
            if self.position > 0:
                self.balance = self.position * current_price
                self.position = 0
                reward = self.balance - 1000  # Profit/loss

        obs = self._get_observation()
        done = False  # Episode ends when balance hits 0 or time limit
        info = {"balance": self.balance, "position": self.position}

        return obs, reward, done, info

Key Design Choices:

[0, 1]

for stable RL training.PPO is a state-of-the-art RL algorithm that balances exploration and exploitation. We’ll use stable-baselines3

:

from stable_baselines3 import PPO
from stable_baselines3.common.env_checker import check_env
from trading_env import TradingEnv

client = Client(API_KEY, API_SECRET)
env = TradingEnv(client)
check_env(env)  # Validate the environment

model = PPO("MlpPolicy", env, verbose=1)
model.learn(total_timesteps=10000)
model.save("trading_bot_ppo")

Training Tips:

total_timesteps

(e.g., 10,000) to validate the setup.

  model.learn(total_timesteps=10000, tb_log_name="ppo_trading")

Replace _get_observation()

with historical data for backtesting:

def _get_observation(self):
    closes = np.load("btc_historical_closes.npy")[-self.window_size:]
    closes = closes / closes.max()
    return closes

Evaluate performance using:

(final_balance - initial_balance) / initial_balance

Example evaluation loop:

def evaluate(model, env, episodes=10):
    returns = []
    for _ in range(episodes):
        obs = env.reset()
        done = False
        episode_return = 0
        while not done:
            action, _ = model.predict(obs)
            obs, reward, done, info = env.step(action)
            episode_return += reward
        returns.append(episode_return)
    return np.mean(returns), np.std(returns)

For live trading, modify the environment to use real-time data:

def _get_observation(self):
    klines = self.client.get_klines(
        symbol=self.symbol, interval=Client.KLINE_INTERVAL_1MINUTE, limit=self.window_size
    )
    closes = [float(k[4]) for k in klines]
    return np.array(closes) / np.max(closes)

Critical safeguards:

Example stop-loss:

def step(self, action):
    current_price = self._get_observation()[-1] * self.max_price
    if action == 1 and self.balance > 0:  # Buy
        self.entry_price = current_price
        self.position = self.balance / current_price
        self.balance = 0
    elif action == 2 and self.position > 0:  # Sell
        self.balance = self.position * current_price
        self.position = 0
    elif self.position > 0 and current_price < self.entry_price * 0.95:  # 5% stop-loss
        self.balance = self.position * current_price
        self.position = 0
    ...

Enhance observations with technical indicators:

def _get_observation(self):
    klines = self.client.get_historical_klines(...)
    closes = np.array([float(k[4]) for k in klines])
    rsi = talib.RSI(closes, timeperiod=14)
    macd = talib.MACD(closes)[0]
    return np.column_stack([closes, rsi, macd])

Use optuna

to optimize RL parameters:

python
import optuna
from stable_baselines3.common.evaluation import evaluate_policy
── more in #machine-learning 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/building-a-self-opti…] indexed:0 read:3min 2026-06-19 ·