QTrader runs one strategy object against a backtest, paper or live gateway
A Light Event-Driven Algorithmic Trading Engine
At a glance
- What is it?
- QTrader is a Python event-driven trading engine where a strategy class is written once and pointed at a backtest gateway, a simulation mode or a live broker gateway by changing the trade mode. It suits a quant developer who wants the same code path in research and production, and it carries a custom licence and a dependency list that needs trimming before you install it.
- Who is it for?
- QTrader fits a quant developer who already trades Hong Kong or Chinese equities through Futu, IB or CQG and wants one strategy file to serve a backtest and a live session, since only the gateway and the trade mode change between them. It is a poor fit for a tick-driven or options strategy, because the engine supports bar data at the moment, and a poor fit for anyone who needs a permissive licence, since setup.py declares the JXW licence rather than an open-source one.
- Can I use it commercially?
- Not without permission. GitHub finds no licence file in the repository, and without a licence all rights are reserved by default: you may read the code but not reuse it. Check the README, or ask the authors, before using it.
- Is it still maintained?
- Yes. The repository last received commits 46 days ago.
- What is it written in?
- Mainly Python, according to GitHub's language statistics.
Answers come from the project's GitHub data, last synced on October 22, 2026, and from our analysis. They are not legal advice.
Editorial analysis
The engine is a bar event loop with gateways around it
QTrader is built around an event engine, and the object that decides what to do is a strategy instance registered with it. The backtest example assembles four parts: a gateway that supplies securities and a time range, an Engine that owns the gateways, a strategy configured with an account name, a version and initial cash, and a BarEventEngineRecorder that captures values over time. The engine is then started with `event_engine.run()` and the process ends with a log line confirming a normal shutdown. The naming tells you the granularity: BarEventEngine, BarEventEngineRecorder, and an `on_bar` callback. That is a deliberate choice, since the engine works on bars rather than ticks, and the documentation states plainly that QTrader supports bar data at the moment. Every design decision about latency, order types and intrabar fills follows from that one constraint, and it is the first thing to check against your own strategy requirements.
Switching to live trading means changing the gateway and one trade mode
The headline claim is that the same code runs in backtest, simulation and live trading, and the mechanism is concrete rather than aspirational. A gateway implements the market data and order entry side, and the engine talks to it uniformly. In backtest the gateway is `BacktestGateway` with a start and end datetime, and the trade mode is set with `gateway.set_trade_mode(TradeMode.BACKTEST)`. For live work the gateway becomes `FutuGateway`, `IbGateway` or `CqgGateway`, and the mode becomes `TradeMode.SIMULATE` or `TradeMode.LIVETRADE`:
# Choose either TradeMode.SIMULATE or TradeMode.LIVETRADE
gateway.set_trade_mode(TradeMode.LIVETRADE)The end timestamp also changes, since a live session needs a future end time for when the program is expected to terminate. This is the part of the design that is genuinely useful, because a strategy that decides orders from a data feed rather than from an internal array does not need a separate live implementation. The caveat is that the backtest gateway and the broker gateway are different code paths that the same interface hides, and the documentation does not describe how orders that cannot be filled are modelled in simulation, so the fidelity of a paper session is worth checking for your own broker.
Data is a folder per security with one CSV per bar date
The data layout is a directory tree rather than a database, and the documentation describes it precisely. You create a folder named after the security you want, and inside it a subfolder whose name encodes the frequency, where K_1M stands for one minute bars. The worked example uses the Hong Kong equity HK.01157 at one minute frequency, and the repository ships a sample under qtrader/examples/data. Inside the frequency folder you place CSV files named by date in yyyy-mm-dd form, each holding OHLCV columns. The path to that tree is then declared in the configuration file rather than inside the package, and the sample configuration shows a dictionary keyed by data type:
DATA_PATH = {
"kline": "path_to_your_qtrader_folder/examples/data/k_line",
}The `kline` key and the `k_line` directory name in the sample value do not match, which is a small inconsistency in the example rather than a documented rule, so treat the key as the thing to copy and the path as the thing to fix. The configuration file itself lives at the working directory as `qtrader_config.py`, and it was moved out of the package in a change dated 2022-09-07, so a config inside the installed package will not be picked up.
A strategy is two methods, and a third one extends the recorded output
The strategy interface is small enough to hold in your head. A strategy subclasses `BaseStrategy` and implements `init_strategy`, called once at startup, and `on_bar`, called on every bar event:
from qtrader.core.strategy import BaseStrategy
class MyStrategy(BaseStrategy):
def init_strategy(self):
pass
def on_bar(self, cur_data:Dict[str, Dict[Security, Bar]]):
print(cur_data)The `cur_data` argument is a nested dictionary keyed by gateway name and then by security, so a strategy that trades more than one instrument reads them from the same callback. Recording extra state is a third method, `get_var`, which returns the variable to log, and the recorder is constructed with the same name:
recorder = BarEventEngineRecorder(var=[])Passing an empty list records the variable at every time step, while passing None records only the last updated value. By default the recorder saves `datetime`, `portfolio_value` and `action` at every step, which is the minimum needed to reconstruct what the strategy did.
A first backtest is a config file, a data folder and one script
Installation is a pip install, and the documentation shows both routes. The recommended path is a conda environment on Python 3.8 or above, then the package from PyPI or from GitHub master:
>> conda create -n qtrader python=3.8
>> conda activate qtrader
# Install stable version from pip (currently version 0.0.4)
>> pip install qtrader
# Alternatively, install latest version from github
>> pip install git+https://github.com/josephchenhk/qtrader@masterThe GitHub route matters here, because the README calls the PyPI version stable at 0.0.4 while setup.py in the repository also declares version 0.0.4 and the badge at the top of the README shows 0.1.0. Three numbers, one of them only in a badge, so a reader who wants the current master code should install from the repository rather than from PyPI. Once installed, the sequence is: create `qtrader_config.py` in the working directory using `qtrader_config_sample.py` as a reference, point `DATA_PATH` at your bar folder, write the strategy, and run the script. Results land in a `qtrader/results` directory named with the latest timestamp, where `result.csv` holds everything the recorder captured and `pnl.html` is an interactive equity curve plot.
The dependency list is far heavier than the engine needs
setup.py declares a runtime set of sqlalchemy, pandas, numpy, pytz, clickhouse-driver, matplotlib, plotly, python-telegram-bot and dash, and the console script entry point is `example=examples.demo_strategy:run`. The requirements.txt file is a different story. It lists well over fifty packages, including tensorflow, keras-nightly, tensorboard, selenium, jupyterlab, scikit-learn, pyfolio, beautifulsoup4, pyarrow, pyautogui and websocket-client, alongside the broker SDKs futu-api and ibapi. For a backtest of a bar strategy, most of that is dead weight, and a few of it, such as selenium and pyautogui, pull in a browser and a screen automation stack. The practical consequence is that a fresh install is slow and that the environment carries software you will never import. A cleaner route is to install the engine with pip and add the broker SDK only if you need live trading, and to keep a separate environment for the notebook-oriented extras so that a dependency conflict in Jupyter tooling cannot break a live session.
The licence is JXW, and the release trail is thin
The licensing situation is the first thing to resolve before any other work. No standard open-source licence appears in the package metadata, and setup.py declares `license='JXW'` with a custom header that says the code may be used, distributed and modified under the terms of the JXW license, which it describes as not likely to be written for another century, and asks that a copy be obtained by email. That is a licence text with no published terms in the repository, so treat it as an obstacle rather than an open-source grant. Two other facts shape the upgrade cost. The repository has no GitHub releases, so there is no version history to point at, and the last push was on 2026-09-07. The README's own latest-update note on that date says the project has been fully upgraded to Systematic Investment Research Intelligence, also referred to as Atabet Quant Intelligence, and points readers to www.atabet.com for more information, without describing what changed in the engine or what the new code requires. The truncated README ends mid-sentence in the live trading section, so the parts of the documentation a live deployment depends on are exactly the parts that are not visible here.
Event-driven against a vectorized backtest, and what each one costs you
The meaningful alternative to QTrader is not another engine but a different way of testing a strategy. A vectorized backtest loads the whole price history into arrays and computes the PnL series in one pass, which is a few lines of pandas and runs in seconds, but the strategy has to be expressible as a vector operation over a column. QTrader instead processes one bar at a time through a callback, which is slower and more verbose, and buys two things in exchange. First, the same callback runs against a live gateway, so a strategy that reacts to the previous bar's state does not have to be rewritten. Second, the event loop matches how many discretionary and systematic workflows actually think, where a decision depends on the running position rather than on a whole series. The costs are concrete. A backtest is slower, the bar data format is a fixed directory layout of CSV files per date, the equity output arrives as a CSV plus an HTML plot rather than a set of statistics you can query, and the framework offers no walk-forward, no parameter sweep and no portfolio-level optimizer in the documented surface. For a researcher who only wants a signal's historical edge, a vectorized script is the better tool. For a trader who intends to run the same logic against a broker, the gateway swap is the reason to accept the extra weight.
Editorial conclusion
QTrader fits a quant developer who already trades Hong Kong or Chinese equities through Futu, IB or CQG and wants one strategy file to serve a backtest and a live session, since only the gateway and the trade mode change between them. It is a poor fit for a tick-driven or options strategy, because the engine supports bar data at the moment, and a poor fit for anyone who needs a permissive licence, since setup.py declares the JXW licence rather than an open-source one. Before installing, check that the version you get is the one you expect, because the README badge reads 0.1.0 while setup.py pins 0.0.4, install from the GitHub master rather than PyPI if you need the newer code, and trim requirements.txt, which ships TensorFlow, Selenium and Jupyter packages that a strategy backtest does not need.
Frequently asked questions
How do I install QTrader?
The README recommends a conda environment on Python 3.8 or above, then pip install qtrader for the stable version, or pip install git+https://github.com/josephchenhk/qtrader@master to install the current master code. The pip release is described as version 0.0.4.
How do I switch a QTrader strategy from backtesting to live trading?
Replace the backtest gateway with a live trading gateway, choosing FutuGateway, IbGateway or CqgGateway, and set the trade mode with gateway.set_trade_mode, using TradeMode.SIMULATE or TradeMode.LIVETRADE. The gateway end timestamp also has to be set to a future time.
What data format does QTrader read for backtesting?
QTrader supports bar data at the moment. You create a folder named after the security, with a subfolder such as K_1M for one minute bars, and put OHLCV CSV files named by date in yyyy-mm-dd form inside it, then point DATA_PATH at that tree in qtrader_config.py.
Where are QTrader backtest results stored?
Results are written to qtrader/results in a folder named with the latest timestamp. The result.csv file holds everything the BarEventEngineRecorder captured, and pnl.html is an interactive plot of the strategy equity curve.
Official sources
Add this badge to your README
If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.
[](https://hysenlabs.com/projects/josephchenhk-qtrader)Was this useful?