Modified README to detail new backtest interface.

This commit is contained in:
Michael Halls-Moore
2015-06-30 09:48:25 +01:00
parent 784cfd2508
commit c273962a04
5 changed files with 56 additions and 41 deletions
+6 -4
View File
@@ -101,17 +101,19 @@ python scripts/generate_simulated_pair.py GBPUSD
At this stage the script is hardcoded to create a single month's data for January 2014. That is, you will see individual files, of the format ```BBBQQQ_YYYYMMDD.csv``` (e.g. ```GBPUSD_20140112.csv```) appear in your ```CSV_DATA_DIR``` for all business days in that month. If you wish to change the month/year of the data output, simply modify the file and re-run. At this stage the script is hardcoded to create a single month's data for January 2014. That is, you will see individual files, of the format ```BBBQQQ_YYYYMMDD.csv``` (e.g. ```GBPUSD_20140112.csv```) appear in your ```CSV_DATA_DIR``` for all business days in that month. If you wish to change the month/year of the data output, simply modify the file and re-run.
7) Now that the historical data has been generated it is possible to carry out a backtest. The backtest file itself is stored in ```backtest/backtest.py```. It currently defaults to trading GBPUSD (so will expect GBPUSD data files in ```CSV_DATA_DIR```), but this can easily be changed. In addition a basic ```MovingAverageCrossStrategy``` is implemented (from ```strategy/strategy.py```), which will backtest on the data found in ```CSV_DATA_DIR```. 7) Now that the historical data has been generated it is possible to carry out a backtest. The backtest file itself is stored in ```backtest/backtest.py```, but this only contains the ```Backtest``` class. To actually execute a backtest you need to instantiate this class and provide it with the necessary modules.
To execute the backtest, simply run the following: The best way to see how this is done is to look at the example Moving Average Crossover implementation in the ```examples/mac.py``` file and use this as a template. This makes use of the ```MovingAverageCrossStrategy``` which is found in ```strategy/strategy.py```. This defaults to trading both GBP/USD and EUR/USD to demonstrate multiple currency pair usage. It uses data found in ```CSV_DATA_DIR```.
To execute the example backtest, simply run the following:
``` ```
python backtest/backtest.py python examples/mac.py
``` ```
**This will take some time.** On my Ubuntu desktop system at home, with the historical data generated via ```generate_simulated_pair.py```, it takes around 5-10 mins to run. A large part of this calculation occurs at the end of the actual backtest, when the drawdown is being calculated, so please remember that the code has not hung up! Please leave it until completion. **This will take some time.** On my Ubuntu desktop system at home, with the historical data generated via ```generate_simulated_pair.py```, it takes around 5-10 mins to run. A large part of this calculation occurs at the end of the actual backtest, when the drawdown is being calculated, so please remember that the code has not hung up! Please leave it until completion.
8) If you wish to view the performance of the backtest you can simply run ```output.py``` to view an equity curve, period returns (i.e. tick-to-tick returns) and a drawdown curve: 8) If you wish to view the performance of the backtest you can simply use ```output.py``` to view an equity curve, period returns (i.e. tick-to-tick returns) and a drawdown curve:
``` ```
python backtest/output.py python backtest/output.py
+1 -1
View File
@@ -18,7 +18,7 @@ class Backtest(object):
self, pairs, data_handler, strategy, self, pairs, data_handler, strategy,
strategy_params, portfolio, execution, strategy_params, portfolio, execution,
equity=100000.0, heartbeat=0.0, equity=100000.0, heartbeat=0.0,
max_iters=100000000 max_iters=10000000000
): ):
""" """
Initialises the backtest. Initialises the backtest.
+3 -3
View File
@@ -4,13 +4,13 @@ from qsforex.backtest.backtest import Backtest
from qsforex.execution.execution import SimulatedExecution from qsforex.execution.execution import SimulatedExecution
from qsforex.portfolio.portfolio import Portfolio from qsforex.portfolio.portfolio import Portfolio
from qsforex import settings from qsforex import settings
from qsforex.strategy.strategy import TestStrategy, MovingAverageCrossStrategy from qsforex.strategy.strategy import MovingAverageCrossStrategy
from qsforex.data.price import HistoricCSVPriceHandler from qsforex.data.price import HistoricCSVPriceHandler
if __name__ == "__main__": if __name__ == "__main__":
# Trade on the "Cable" - GBP/USD # Trade on GBP/USD and EUR/USD
pairs = ["GBPUSD"] pairs = ["GBPUSD", "EURUSD"]
# Create the strategy parameters for the # Create the strategy parameters for the
# MovingAverageCrossStrategy # MovingAverageCrossStrategy
+13 -12
View File
@@ -14,9 +14,9 @@ from qsforex.settings import OUTPUT_RESULTS_DIR
class Portfolio(object): class Portfolio(object):
def __init__( def __init__(
self, ticker, events, home_currency="GBP", leverage=20, self, ticker, events, home_currency="GBP",
equity=Decimal("100000.00"), risk_per_trade=Decimal("0.02"), leverage=20, equity=Decimal("100000.00"),
backtest=True risk_per_trade=Decimal("0.02"), backtest=True
): ):
self.ticker = ticker self.ticker = ticker
self.events = events self.events = events
@@ -28,7 +28,8 @@ class Portfolio(object):
self.backtest = backtest self.backtest = backtest
self.trade_units = self.calc_risk_position_size() self.trade_units = self.calc_risk_position_size()
self.positions = {} self.positions = {}
self.backtest_file = self.create_equity_file() if self.backtest:
self.backtest_file = self.create_equity_file()
def calc_risk_position_size(self): def calc_risk_position_size(self):
return self.equity * self.risk_per_trade return self.equity * self.risk_per_trade
@@ -114,16 +115,16 @@ class Portfolio(object):
if currency_pair in self.positions: if currency_pair in self.positions:
ps = self.positions[currency_pair] ps = self.positions[currency_pair]
ps.update_position_price() ps.update_position_price()
out_line = "%s,%s" % (tick_event.time, self.balance)
for pair in self.ticker.pairs:
if pair in self.positions:
out_line += ",%s" % self.positions[currency_pair].profit_base
else:
out_line += ",0.00"
out_line += "\n"
if self.backtest: if self.backtest:
out_line = "%s,%s" % (tick_event.time, self.balance)
for pair in self.ticker.pairs:
if pair in self.positions:
out_line += ",%s" % self.positions[pair].profit_base
else:
out_line += ",0.00"
out_line += "\n"
print(out_line[:-2]) print(out_line[:-2])
self.backtest_file.write(out_line) self.backtest_file.write(out_line)
def execute_signal(self, signal_event): def execute_signal(self, signal_event):
side = signal_event.side side = signal_event.side
+32 -20
View File
@@ -1,3 +1,5 @@
import copy
from qsforex.event.event import SignalEvent from qsforex.event.event import SignalEvent
@@ -52,39 +54,49 @@ class MovingAverageCrossStrategy(object):
short_window=500, long_window=2000 short_window=500, long_window=2000
): ):
self.pairs = pairs self.pairs = pairs
self.pairs_dict = self.create_pairs_dict()
self.events = events self.events = events
self.ticks = 0
self.invested = False
self.short_window = short_window self.short_window = short_window
self.long_window = long_window self.long_window = long_window
self.short_sma = None
self.long_sma = None 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): def calc_rolling_sma(self, sma_m_1, window, price):
return ((sma_m_1 * (window - 1)) + price) / window return ((sma_m_1 * (window - 1)) + price) / window
def calculate_signals(self, event): def calculate_signals(self, event):
if event.type == 'TICK': if event.type == 'TICK':
pair = event.instrument
price = event.bid price = event.bid
if self.ticks == 0: pd = self.pairs_dict[pair]
self.short_sma = price if pd["ticks"] == 0:
self.long_sma = price pd["short_sma"] = price
pd["long_sma"] = price
else: else:
self.short_sma = self.calc_rolling_sma( pd["short_sma"] = self.calc_rolling_sma(
self.short_sma, self.short_window, price pd["short_sma"], self.short_window, price
) )
self.long_sma = self.calc_rolling_sma( pd["long_sma"] = self.calc_rolling_sma(
self.long_sma, self.long_window, price pd["long_sma"], self.long_window, price
) )
# Only start the strategy when we have created an accurate short window # Only start the strategy when we have created an accurate short window
if self.ticks > self.short_window: if pd["ticks"] > self.short_window:
if self.short_sma > self.long_sma and not self.invested: if pd["short_sma"] > pd["long_sma"] and not pd["invested"]:
signal = SignalEvent(self.pairs[0], "market", "buy", event.time) signal = SignalEvent(pair, "market", "buy", event.time)
self.events.put(signal) self.events.put(signal)
self.invested = True pd["invested"] = True
if self.short_sma < self.long_sma and self.invested: if pd["short_sma"] < pd["long_sma"] and pd["invested"]:
signal = SignalEvent(self.pairs[0], "market", "sell", event.time) signal = SignalEvent(pair, "market", "sell", event.time)
self.events.put(signal) self.events.put(signal)
self.invested = False pd["invested"] = False
self.ticks += 1 pd["ticks"] += 1