# Forex Trading Diary #7 - New Backtest Interface

## By Michael Halls-Moore on June 30th, 2015

Although I've spent the majority of this month researching time series analysis for the upcoming article series, I've also been working on QSForex attempting to improve the API somewhat.

In particular I've made the interface for beginning a new backtest a lot simpler by encapsulating a lot of the "boilerplate" code into a new Backtest class. I've also modified the system to be fully workable with multiple currency pairs. In this article I'll describe the new interface and show the usual Moving Average Crossover example on both GBP/USD and EUR/USD.

## New Backtest Interface

I've modified the backtest interface so that instead of having to create a custom backtest.py file as before, you can simply create an instance of Backtest and populate it with your trading components.

The best way to get started with the new approach is to take a look in the examples/ directory and open up mac.py:

from __future__ import print_function

from qsforex.backtest.backtest import Backtest
from qsforex.execution.execution import SimulatedExecution
from qsforex.portfolio.portfolio import Portfolio
from qsforex import settings
from qsforex.strategy.strategy import MovingAverageCrossStrategy
from qsforex.data.price import HistoricCSVPriceHandler

if __name__ == "__main__":
# Trade on GBP/USD and EUR/USD
pairs = ["GBPUSD", "EURUSD"]

# Create the strategy parameters for the
# MovingAverageCrossStrategy
strategy_params = {
"short_window": 500,
"long_window": 2000
}

# Create and execute the backtest
backtest = Backtest(
pairs, HistoricCSVPriceHandler,
MovingAverageCrossStrategy, strategy_params,
Portfolio, SimulatedExecution,
equity=settings.EQUITY
)


As you can see it is relatively short. Firstly the code imports the necessary components, namely the Backtest, SimulatedExecution, Portfolio, MovingAverageCrossStrategy and HistoricCSVPriceHandler.

Secondly, we define the pairs that we'll be trading with and then create a dictionary known as strategy_params. This essentially contains any keyword arguments that we may wish to pass to our strategy. In the case of a Moving Average Crossover we need to pass the rolling window lengths. These values are in terms of "ticks".

Finally we create a Backtest instance and pass all of the objects as parameters. Then, we run the backtest itself.

Within the new backtest.py we call this method:

# backtest.py
..
..
"""
Simulates the backtest and outputs portfolio performance.
"""
self._run_backtest()
self._output_performance()
print("Backtest complete.")


It carries out the backtest calculation (i.e. portfolio updating as ticks come in) as well as computing and outputting the performance in equity.csv.

As before we can still produce a plot of the output with the backtest/output.py script. I'll use this script below when we discuss multiple currency pair implementation.

## Multiple Currency Pairs

We're finally at the point where we can test our first non-trivial trading strategy on high-frequency tick data across multiple currency pairs!

To achieve this I've modified how the MovingAverageCrossStrategy is handled. For completeness I've put the full listing below:

class MovingAverageCrossStrategy(object):
"""
A basic Moving Average Crossover strategy that generates
two simple moving averages (SMA), with default windows
of 500 ticks for the short SMA and 2,000 ticks for the
long SMA.

The strategy is "long only" in the sense it will only
open a long position once the short SMA exceeds the long
SMA. It will close the position (by taking a corresponding
sell order) when the long SMA recrosses the short SMA.

The strategy uses a rolling SMA calculation in order to
increase efficiency by eliminating the need to call two
full moving average calculations on each tick.
"""
def __init__(
self, pairs, events,
short_window=500, long_window=2000
):
self.pairs = pairs
self.pairs_dict = self.create_pairs_dict()
self.events = events
self.short_window = short_window
self.long_window = long_window

def create_pairs_dict(self):
attr_dict = {
"ticks": 0,
"invested": False,
"short_sma": None,
"long_sma": None
}
pairs_dict = {}
for p in self.pairs:
pairs_dict[p] = copy.deepcopy(attr_dict)
return pairs_dict

def calc_rolling_sma(self, sma_m_1, window, price):
return ((sma_m_1 * (window - 1)) + price) / window

def calculate_signals(self, event):
if event.type == 'TICK':
pair = event.instrument
price = event.bid
pd = self.pairs_dict[pair]
if pd["ticks"] == 0:
pd["short_sma"] = price
pd["long_sma"] = price
else:
pd["short_sma"] = self.calc_rolling_sma(
pd["short_sma"], self.short_window, price
)
pd["long_sma"] = self.calc_rolling_sma(
pd["long_sma"], self.long_window, price
)
# Only start the strategy when we have created an accurate short window
if pd["ticks"] > self.short_window:
if pd["short_sma"] > pd["long_sma"] and not pd["invested"]:
signal = SignalEvent(pair, "market", "buy", event.time)
self.events.put(signal)
pd["invested"] = True
if pd["short_sma"] < pd["long_sma"] and pd["invested"]:
signal = SignalEvent(pair, "market", "sell", event.time)
self.events.put(signal)
pd["invested"] = False
pd["ticks"] += 1


Essentially we create an attribute dictionary attr_dict that stores the number of elapsed ticks and whether the strategy is "in" the market for that particular pair.

In calculate_signals we wait for a TickEvent to be received and then calculate the rolling Simple Moving Averages for the short and long windows. Once we exceed the short window for a particular pair, the strategy goes long and exits in the same manner as before, although for each pair separately.

I have made use of 2 months of data for both GBP/USD and EUR/USD and the backtest takes quite some time to execute! It takes around 10-15 minutes on my system, including the calculation of drawdown.

However, once the backtest is complete we're able to use backtest/output.py to produce the following performance chart:

Performance chart of the GBP/USD and EUR/USD MovingAverageCrossover for the period April 2015 - May 2015.

Clearly the performance is not great as the strategy remains almost entirely "underwater" as time goes on. That being said, we shouldn't expect much from such a basic strategy on high-frequency tick data. In the future we are going to be looking at far more sophisticated approaches to trading at this time scale.

Hopefully it will provide a useful starting point for developing more sophisticated strategies. I'm looking forward to seeing what others come up with in the near future!

## Next Steps

At this stage there is a list of issues over on Github that need attention. I'm going to be slowly working through those in the next month.

In particular I would like to make the system a lot faster, since it will allow parameter searches to be carried out in a reasonable time. While Python is a great tool, it's one drawback is that it is relatively slow when compared to C/C++. Hence I will be carrying out a lot of profiling to try and improve the execution speed of both the backtest and the performance calculations.

In addition, I've had some comments from people suggesting that they'd like to see more varied order types than the simple Market Order. For carrying out proper HFT strategies against OANDA we are going to need to use Limit Orders. This will probably require a reworking of how the system currently executes trades, but it will allow a much bigger universe of trading strategies to be carried out.

Please get in touch at mike@quantstart.com if you have any suggestions, comments or bug reports. I'm always eager to hear how people have ended up using QSForex and what modifications have been made.

# Just Getting Started with Quantitative Trading?

## 3 Reasons to Subscribe to the QuantStart Email List:

No Thanks, I'll Pass For Now