Writing Agents

Complete guide to building AI trading agents on Qynt Labs.

Writing Agents

This guide covers everything you need to build trading agents on Qynt Labs. Agents are written in Python using Backtrader, a popular backtesting framework. If you’re familiar with Python, you’ll feel right at home.


Agent Requirements

Before your agent can run on Qynt Labs, it must meet specific naming and structural requirements. The platform uses these conventions to locate and execute your code.

Every agent must follow these requirements:

Requirement Value
Filename agent.py (exactly)
Class name Agent (exactly)
Base class bt.Strategy
Required methods __init__(), prenext(), next()

Basic Structure

Every agent follows the same basic template. You define parameters for configuration, initialize indicators in __init__(), and implement your trading logic in next(). The prenext() method ensures your agent can start trading before all indicators have accumulated enough data.

import backtrader as bt

class Agent(bt.Strategy):
    """Your agent description here."""

    # Optional: define tunable parameters
    params = (
        ('period', 20),
        ('threshold', 0.02),
    )

    def __init__(self):
        """Initialize indicators and state. Runs once at startup."""
        self.sma = bt.indicators.SMA(self.data.close, period=self.p.period)

    def prenext(self):
        """Called before all indicator data is available. Must call next()."""
        self.next()

    def next(self):
        """Called on each new bar. Your trading logic goes here."""
        pass

The prenext() Method

Every indicator has a minimum period—the number of bars required before it can produce valid values. A 20-period SMA (Simple Moving Average) needs 20 bars of data before it can calculate a meaningful result. By default, Backtrader calls prenext() while accumulating data, and only calls next() once all indicators have reached their minimum period.

To start trading before all indicators are ready, implement prenext() to call next():

def prenext(self):
    self.next()

Inside next(), check if your indicator has valid data before using it:

class Agent(bt.Strategy):
    params = (
        ('sma_period', 20),  # Number of bars for the Simple Moving Average
    )

    def __init__(self):
        # Create the indicator in __init__
        self.sma = bt.indicators.SMA(self.data.close, period=self.p.sma_period)

    def prenext(self):
        self.next()

    def next(self):
        # Check if SMA has enough data
        if len(self.data) < self.p.sma_period:
            return  # Skip until we have enough data

        # Now safe to use the indicator
        if self.data.close[0] > self.sma[0]:
            self.buy()

Parameters

Parameters let you configure your agent’s behavior without changing code. Define them as a tuple of name-value pairs, then access them throughout your agent using self.p or self.params. This makes it easy to experiment with different settings during backtesting.

Use the params tuple to define tunable parameters:

class Agent(bt.Strategy):
    params = (
        ('fast_period', 10),
        ('slow_period', 30),
        ('size', 100),
    )

    def __init__(self):
        # Access params with self.p or self.params
        self.fast_ma = bt.indicators.SMA(period=self.p.fast_period)
        self.slow_ma = bt.indicators.SMA(period=self.p.slow_period)

    def next(self):
        self.buy(size=self.p.size)

Accessing Data

Your agent receives data through self.data, which can include market prices, news, social media sentiment, geospatial data, and more. You can access the current bar using index [0] and previous bars using negative indices like [-1] for one bar ago.

Current and Historical Prices

def next(self):
    # Current bar (index 0)
    current_close = self.data.close[0]
    current_open = self.data.open[0]
    current_high = self.data.high[0]
    current_low = self.data.low[0]
    current_volume = self.data.volume[0]

    # Previous bars (negative index)
    previous_close = self.data.close[-1]  # 1 bar ago
    two_bars_ago = self.data.close[-2]    # 2 bars ago

Data Length

def next(self):
    # Number of bars processed so far
    bars_available = len(self.data)

    if bars_available < 20:
        return  # Not enough data yet

Indicators

Indicators are pre-built calculations that analyze price data to identify trends, momentum, and volatility. Backtrader includes dozens of built-in indicators that you can use in your agents. Always define indicators in __init__() so they’re calculated automatically as new data arrives.

Backtrader includes many built-in indicators. Define them in __init__():

Moving Averages

def __init__(self):
    self.sma = bt.indicators.SMA(self.data.close, period=20)   # Simple Moving Average
    self.ema = bt.indicators.EMA(self.data.close, period=20)   # Exponential Moving Average
    self.wma = bt.indicators.WMA(self.data.close, period=20)   # Weighted Moving Average

Momentum Indicators

def __init__(self):
    self.rsi = bt.indicators.RSI(self.data.close, period=14)    # Relative Strength Index
    self.macd = bt.indicators.MACD(self.data.close)             # Moving Average Convergence Divergence
    self.stoch = bt.indicators.Stochastic(self.data)

Volatility Indicators

def __init__(self):
    self.atr = bt.indicators.ATR(self.data, period=14)          # Average True Range
    self.bbands = bt.indicators.BollingerBands(self.data.close)

Using Indicators

def next(self):
    # Access indicator values like data
    current_sma = self.sma[0]
    previous_sma = self.sma[-1]

    # RSI example
    if self.rsi[0] < 30:  # Oversold
        self.buy()
    elif self.rsi[0] > 70:  # Overbought
        self.sell()

    # Bollinger Bands
    if self.data.close[0] < self.bbands.bot[0]:
        self.buy()

See the Backtrader indicator reference for the full list.


Placing Orders

When your agent identifies a trading opportunity, it places orders using built-in methods like buy(), sell(), and close(). You can place simple market orders or specify prices for limit and stop orders. Orders are executed on the next available bar.

Basic Orders

def next(self):
    # Market buy
    self.buy()

    # Market sell
    self.sell()

    # Close entire position
    self.close()

Order with Size

def next(self):
    # Buy 100 shares
    self.buy(size=100)

    # Sell 50 shares
    self.sell(size=50)

Limit and Stop Orders

def next(self):
    # Limit order: buy at specific price
    self.buy(size=100, price=150.00, exectype=bt.Order.Limit)

    # Stop order: sell if price falls to level
    self.sell(size=100, price=145.00, exectype=bt.Order.Stop)

    # Stop-limit order
    self.buy(size=100, price=155.00, pricelimit=156.00,
             exectype=bt.Order.StopLimit)

Position Management

Your agent needs to know its current holdings to make smart trading decisions. The self.position object tells you whether you’re in a trade, how many shares you hold, and at what average price. Use this information to avoid duplicate orders or to implement exit logic.

Checking Position

def next(self):
    # Check if we have a position
    if self.position:
        print(f"Position size: {self.position.size}")
        print(f"Average price: {self.position.price}")
    else:
        print("No position")

Position-Based Logic

def next(self):
    # Only buy if not in a position
    if not self.position:
        if self.buy_signal():
            self.buy()

    # Only sell if in a long position
    if self.position.size > 0:
        if self.sell_signal():
            self.sell()

Complete Example

This section brings everything together with a fully working agent. The example below uses Amazon’s Chronos time-series forecasting model to predict price movements. It looks at the last 20 bars of price data, generates a one-step-ahead forecast, and compares that prediction to the current price. If the model predicts the price will rise, it buys using 15% of available cash; if it predicts a decline while holding a position, it closes that position.

Warning:

This example is public and available to everyone, so don't expect it to give you an edge on its own. Use it as a starting point to understand the structure, then build your own unique agent with different models, indicators, and logic. The best-performing agents on the platform are the ones developers have customized and refined.

import backtrader as bt
from chronos import ChronosPipeline
import torch

class Agent(bt.Strategy):
    params = (
        ('lookback', 20),                        # Number of bars to use for prediction
        ('model_name', 'amazon/chronos-t5-small'),  # Chronos model variant
    )

    def __init__(self):
        self.forecaster = ChronosPipeline.from_pretrained(
            self.params.model_name,
            device_map="auto",
            torch_dtype=torch.float32
        )

    def prenext(self):
        self.next()

    def next(self):
        for d in self.datas:
            if len(d) < self.params.lookback:
                continue

            prices = list(d.close.get(size=self.params.lookback))
            context = torch.tensor(prices).unsqueeze(0)

            forecast = self.forecaster.predict(context, prediction_length=1)
            predicted = forecast[0].mean().item()

            pos = self.getposition(d).size
            if not pos and predicted > d.close[0]:
                size = int(self.broker.getcash() * 0.15 / d.close[0])
                self.buy(data=d, size=size)
            elif pos and predicted < d.close[0]:
                self.close(data=d)

FMEL Explainability

Qynt Labs maintains a list of approved AI models that we have archived, ensuring we can always recreate exactly how your agent behaved. Our Foundation Model Explainability Layer (FMEL) automatically records every data field your agent accesses, the exact timestamps of each access, all orders placed and executed, and the portfolio state at each decision point. You don’t need to add any code—it just works.

This infrastructure enables explainability for AI-driven trading decisions. We handle all regulatory compliance questions on your behalf—you just build the agent.


Best Practices

Follow these guidelines to build robust, maintainable agents that perform well in backtesting and paper trading.

Practice Description
Always implement prenext() Call self.next() to start trading immediately.
Check data availability Verify indicators have enough data before using them.
Use parameters Make your agent tunable via params.
Handle pending orders Track order state to avoid duplicate orders.
Use approved models only Agents using unapproved AI models will fail during backtesting.
Start simple Get a basic agent working before adding complexity.

Further Reading

Want to dive deeper? These resources provide comprehensive documentation on Backtrader’s features and a community forum where you can ask questions and share ideas.