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.
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.
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
+1 -1
View File
@@ -18,7 +18,7 @@ class Backtest(object):
self, pairs, data_handler, strategy,
strategy_params, portfolio, execution,
equity=100000.0, heartbeat=0.0,
max_iters=100000000
max_iters=10000000000
):
"""
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.portfolio.portfolio import Portfolio
from qsforex import settings
from qsforex.strategy.strategy import TestStrategy, MovingAverageCrossStrategy
from qsforex.strategy.strategy import MovingAverageCrossStrategy
from qsforex.data.price import HistoricCSVPriceHandler
if __name__ == "__main__":
# Trade on the "Cable" - GBP/USD
pairs = ["GBPUSD"]
# Trade on GBP/USD and EUR/USD
pairs = ["GBPUSD", "EURUSD"]
# Create the strategy parameters for the
# MovingAverageCrossStrategy
+13 -12
View File
@@ -14,9 +14,9 @@ from qsforex.settings import OUTPUT_RESULTS_DIR
class Portfolio(object):
def __init__(
self, ticker, events, home_currency="GBP", leverage=20,
equity=Decimal("100000.00"), risk_per_trade=Decimal("0.02"),
backtest=True
self, ticker, events, home_currency="GBP",
leverage=20, equity=Decimal("100000.00"),
risk_per_trade=Decimal("0.02"), backtest=True
):
self.ticker = ticker
self.events = events
@@ -28,7 +28,8 @@ class Portfolio(object):
self.backtest = backtest
self.trade_units = self.calc_risk_position_size()
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):
return self.equity * self.risk_per_trade
@@ -114,16 +115,16 @@ class Portfolio(object):
if currency_pair in self.positions:
ps = self.positions[currency_pair]
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:
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])
self.backtest_file.write(out_line)
self.backtest_file.write(out_line)
def execute_signal(self, signal_event):
side = signal_event.side
+33 -21
View File
@@ -1,3 +1,5 @@
import copy
from qsforex.event.event import SignalEvent
@@ -52,39 +54,49 @@ class MovingAverageCrossStrategy(object):
short_window=500, long_window=2000
):
self.pairs = pairs
self.events = events
self.ticks = 0
self.invested = False
self.pairs_dict = self.create_pairs_dict()
self.events = events
self.short_window = short_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):
return ((sma_m_1 * (window - 1)) + price) / window
def calculate_signals(self, event):
if event.type == 'TICK':
pair = event.instrument
price = event.bid
if self.ticks == 0:
self.short_sma = price
self.long_sma = price
pd = self.pairs_dict[pair]
if pd["ticks"] == 0:
pd["short_sma"] = price
pd["long_sma"] = price
else:
self.short_sma = self.calc_rolling_sma(
self.short_sma, self.short_window, price
pd["short_sma"] = self.calc_rolling_sma(
pd["short_sma"], self.short_window, price
)
self.long_sma = self.calc_rolling_sma(
self.long_sma, self.long_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 self.ticks > self.short_window:
if self.short_sma > self.long_sma and not self.invested:
signal = SignalEvent(self.pairs[0], "market", "buy", event.time)
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)
self.invested = True
if self.short_sma < self.long_sma and self.invested:
signal = SignalEvent(self.pairs[0], "market", "sell", event.time)
pd["invested"] = True
if pd["short_sma"] < pd["long_sma"] and pd["invested"]:
signal = SignalEvent(pair, "market", "sell", event.time)
self.events.put(signal)
self.invested = False
self.ticks += 1
pd["invested"] = False
pd["ticks"] += 1