What Are CCXT and Backtrader?
CCXT is an open-source project designed for cryptocurrency exchanges. It provides a unified API interface that enables users to trade across different exchanges. In this context, we'll use CCXT's Python interface for trading.
Backtrader is a Python-based quantitative trading backtesting framework known for its simplicity and ease of use.
Both libraries can be easily installed via pip:
pip install ccxt backtrader
Using CCXT
Several key concepts in CCXT:
- Exchange: The trading platform, including Binance, OKX, etc. Here, we'll use Binance's interface.
- Symbol: The trading pair, such as BTC/USDT or ETH/USDT. The first currency is the base currency, while the second is the quote currency. For example, BTC/USDT represents the price of one BTC in USDT.
Creating an Exchange Object
To use CCXT, you first need to create an exchange object. Here's how to do it for Binance:
exchange = ccxt.binance({
"enableRateLimit": True,
"proxy": {
"http": "127.0.0.1:7890",
"https": "127.0.0.1:7890"
}
})
enableRateLimit=True
enables rate limiting to prevent IP bans due to excessive requests.- The proxy settings should be replaced with your own proxy server and port.
For fetching market data, API keys aren't required. However, for trading or account queries, you'll need them:
exchange.apiKey = "your api key"
exchange.secret = "your secret key"
# Test
balance = exchange.fetch_balance()
# If no error occurs, the API key and secret are correct
Fetching Historical Data
To query historical data, use exchange.fetch_ohlcv
. OHLCV stands for Open, High, Low, Close, Volume. Here's how to fetch daily data:
symbol = "BTC/USDT"
time_interval = '1d'
since_time = datetime(2021, 1, 1)
to_time = datetime(2024, 8, 1)
df = pd.DataFrame()
while since_time < to_time:
since = exchange.parse8601(since_time.strftime("%Y-%m-%d %H:%M:%S"))
data = exchange.fetch_ohlcv(symbol=symbol,
timeframe=time_interval,
since=since,
limit=500)
new_df = pd.DataFrame(data, dtype=float)
new_df.rename(columns={0: 'MTS', 1: 'open', 2: 'high', 3: 'low', 4: 'close', 5: 'volume'}, inplace=True)
new_df['candle_begin_time'] = pd.to_datetime(new_df['MTS'], unit='ms')
new_df['candle_begin_time_GMT8'] = new_df['candle_begin_time'] + timedelta(hours=8)
new_df = new_df[['candle_begin_time_GMT8', 'open', 'high', 'low', 'close', 'volume']]
df = pd.concat([df, new_df], ignore_index=True)
since_time = df['candle_begin_time_GMT8'].iloc[-1] + timedelta(days=1)
df.to_csv(dataset_path, index=False)
The resulting CSV file will have six columns: timestamp, open price, high price, low price, closing price, and volume.
๐ Learn more about cryptocurrency trading strategies
Backtrader Backtesting
Backtrader operates around a Cerebro
(Spanish for "brain") instance. The general workflow is:
- Create a Cerebro instance.
- Add data.
- Add strategies.
- Run backtests.
Data Sources: backtrader.feeds
Backtrader uses feeds to provide data sources. These feeds could be from Yahoo Finance, CSV files, or Pandas DataFrames. Here, we'll use the CSV file created earlier:
data = btfeeds.GenericCSVData(
dataname=dataset_path,
timeframe=bt.TimeFrame.Minutes,
fromdate=datetime(2021, 1, 1),
todate=datetime(2024, 8, 1),
nullvalue=0.0,
dtformat=('%Y-%m-%d %H:%M:%S'),
datetime=0,
open=1,
high=2,
low=3,
close=4,
volume=5,
openinterest=-1
)
Strategies: backtrader.Strategy
Create your own strategy by inheriting from bt.Strategy
:
class TestStrategy(bt.Strategy):
params = (
("maperiod", 15),
("printlog", False),
)
def log(self, txt, dt=None, doprint=False):
if self.params.printlog or doprint:
dt = dt or self.datas[0].datetime.date(0)
logging.info("{}, {}".format(dt.isoformat(), txt))
def __init__(self):
self.dataclose = self.datas[0].close
self.order = None
self.buyprice = None
self.buycomm = None
self.sma = btind.SimpleMovingAverage(
self.datas[0], period=self.params.maperiod)
def notify_order(self, order):
if order.status in [order.Submitted, order.Accepted]:
return
if order.status in [order.Completed]:
if order.isbuy():
self.log("BUY EXECUTED, Price: {:.2f}, Cost: {:.2f}, Comm: {:.2f}".format(
order.executed.price,
order.executed.value,
order.executed.comm),
doprint=True)
else:
self.log("SELL EXECUTED, Price: {:.2f}, Cost: {:.2f}, Comm: {:.2f}".format(
order.executed.price,
order.executed.value,
order.executed.comm),
doprint=True)
def notify_trade(self, trade):
if not trade.isclosed:
return
self.log("OPERATION PROFIT, GROSS: {:.2f}, NET: {:.2f}".format(
trade.pnl, trade.pnlcomm),
doprint=True)
def next(self):
self.log("Close, {:.2f}".format(self.dataclose[0]))
if self.order:
return
if not self.position:
if self.dataclose[0] > self.sma[0]:
self.log("BUY CREATE, {:.2f}".format(self.dataclose[0]))
self.order = self.buy()
else:
if self.dataclose[0] < self.sma[0]:
self.log("SELL CREATE, {:.2f}".format(self.dataclose[0]))
self.order = self.sell()
def stop(self):
self.log("MA Period: {}, Ending Value: {}".format(self.params.maperiod, self.broker.getvalue()),
doprint=True)
This strategy implements a simple moving average crossover system:
- Buy when price crosses above the moving average
- Sell when price crosses below the moving average
Additional Cerebro Settings
cerebro = bt.Cerebro()
cerebro.addstrategy(TestStrategy)
cerebro.adddata(data)
cerebro.broker.setcash(1000)
cerebro.broker.setcommission(commission=0.001)
cerebro.addsizer(bt.sizers.FixedSize, stake=0.01)
cerebro.addanalyzer(bt.analyzers.PyFolio, _name='pyfolio')
cerebro.run()
cerebro.plot()
For performance analysis, we use QuantStats:
returns = cerebro.run()
pyfoliozer = result[0].analyzers.getbyname('pyfolio')
returns, positions, transactions, gross_lev = pyfoliozer.get_pf_items()
qs.reports.html(returns, output='backtest.html', title='Backtest Report')
๐ Discover advanced trading techniques
FAQ
1. What's the difference between CCXT and Backtrader?
CCXT focuses on connecting to exchanges and fetching market data, while Backtrader specializes in strategy backtesting and execution.
2. Do I need API keys to fetch historical data?
No, API keys are only required for trading or accessing account-specific information.
3. What brokers does Backtrader support?
Backtrader has built-in support for simulated brokers. For live trading, you can connect to various brokers through CCXT or other interfaces.
4. Can I use Backtrader for stocks or forex?
Yes, Backtrader is asset-class agnostic. While our example uses cryptocurrency, it works equally well with stocks, forex, or other instruments.
5. How do I optimize my strategy parameters?