From 784cfd2508bf60df66fd12a6ece700d1926324ff Mon Sep 17 00:00:00 2001 From: Michael Halls-Moore Date: Tue, 23 Jun 2015 11:52:44 +0100 Subject: [PATCH] Added a Backtest class, which replaces the script in backtest.py. Also added an examples directory, to make strategy testing straightforward. --- backtest/backtest.py | 134 ++++++++++++++++++++++--------------------- examples/__init__.py | 0 examples/mac.py | 29 ++++++++++ 3 files changed, 97 insertions(+), 66 deletions(-) create mode 100644 examples/__init__.py create mode 100644 examples/mac.py diff --git a/backtest/backtest.py b/backtest/backtest.py index 4f84758..88446c7 100644 --- a/backtest/backtest.py +++ b/backtest/backtest.py @@ -1,81 +1,83 @@ from __future__ import print_function -import copy try: import Queue as queue except ImportError: import queue -import threading import time -from decimal import Decimal, getcontext -from qsforex.execution.execution import SimulatedExecution -from qsforex.portfolio.portfolio import Portfolio from qsforex import settings -from qsforex.strategy.strategy import TestStrategy, MovingAverageCrossStrategy -from qsforex.data.price import HistoricCSVPriceHandler -def backtest( - events, ticker, strategy, portfolio, - execution, heartbeat, max_iters=5000000 +class Backtest(object): + """ + Enscapsulates the settings and components for carrying out + an event-driven backtest on the foreign exchange markets. + """ + def __init__( + self, pairs, data_handler, strategy, + strategy_params, portfolio, execution, + equity=100000.0, heartbeat=0.0, + max_iters=100000000 ): - """ - Carries out an infinite while loop that polls the - events queue and directs each event to either the - strategy component of the execution handler. The - loop will then pause for "heartbeat" seconds and - continue unti the maximum number of iterations is - exceeded. - """ - iters = 0 - while iters < max_iters and ticker.continue_backtest: - try: - event = events.get(False) - except queue.Empty: - ticker.stream_next_tick() - else: - if event is not None: - if event.type == 'TICK': - strategy.calculate_signals(event) - portfolio.update_portfolio(event) - elif event.type == 'SIGNAL': - portfolio.execute_signal(event) - elif event.type == 'ORDER': - execution.execute_order(event) - time.sleep(heartbeat) - iters += 1 - portfolio.output_results() + """ + Initialises the backtest. + """ + self.pairs = pairs + self.events = queue.Queue() + self.csv_dir = settings.CSV_DATA_DIR + self.ticker = data_handler(self.pairs, self.events, self.csv_dir) + self.strategy_params = strategy_params + self.strategy = strategy( + self.pairs, self.events, **self.strategy_params + ) + self.equity = equity + self.heartbeat = heartbeat + self.max_iters = max_iters + self.portfolio = portfolio( + self.ticker, self.events, equity=self.equity, backtest=True + ) + self.execution = execution() + def _run_backtest(self): + """ + Carries out an infinite while loop that polls the + events queue and directs each event to either the + strategy component of the execution handler. The + loop will then pause for "heartbeat" seconds and + continue unti the maximum number of iterations is + exceeded. + """ + print("Running Backtest...") + iters = 0 + while iters < self.max_iters and self.ticker.continue_backtest: + try: + event = self.events.get(False) + except queue.Empty: + self.ticker.stream_next_tick() + else: + if event is not None: + if event.type == 'TICK': + self.strategy.calculate_signals(event) + self.portfolio.update_portfolio(event) + elif event.type == 'SIGNAL': + self.portfolio.execute_signal(event) + elif event.type == 'ORDER': + self.execution.execute_order(event) + time.sleep(self.heartbeat) + iters += 1 -if __name__ == "__main__": - heartbeat = 0.0 - events = queue.Queue() - equity = settings.EQUITY + def _output_performance(self): + """ + Outputs the strategy performance from the backtest. + """ + print("Calculating Performance Metrics...") + self.portfolio.output_results() - # Load the historic CSV tick data filesw - pairs = ["GBPUSD"] - csv_dir = settings.CSV_DATA_DIR - if csv_dir is None: - print("No historic data directory provided - backtest terminating.") - sys.exit() - - # Create the historic tick data streaming class - ticker = HistoricCSVPriceHandler(pairs, events, csv_dir) - - # Create the strategy/signal generator, passing the - # instrument and the events queue - strategy = MovingAverageCrossStrategy( - pairs, events, 500, 2000 - ) - - # Create the portfolio object to track trades - portfolio = Portfolio( - ticker, events, equity=equity, backtest=True - ) - - # Create the simulated execution handler - execution = SimulatedExecution() - - # Carry out the backtest loop - backtest(events, ticker, strategy, portfolio, execution, heartbeat) + def simulate_trading(self): + """ + Simulates the backtest and outputs portfolio performance. + """ + self._run_backtest() + self._output_performance() + print("Backtest complete.") diff --git a/examples/__init__.py b/examples/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/examples/mac.py b/examples/mac.py new file mode 100644 index 0000000..4a1a059 --- /dev/null +++ b/examples/mac.py @@ -0,0 +1,29 @@ +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 TestStrategy, MovingAverageCrossStrategy +from qsforex.data.price import HistoricCSVPriceHandler + + +if __name__ == "__main__": + # Trade on the "Cable" - GBP/USD + pairs = ["GBPUSD"] + + # 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 + ) + backtest.simulate_trading() \ No newline at end of file