Modified README to detail new backtest interface.
This commit is contained in:
@@ -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
|
||||
|
||||
@@ -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
@@ -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
@@ -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
@@ -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
|
||||
Reference in New Issue
Block a user