Added the ability for the backtester to use unrealised PnL from the Position objects to calculate a tick-by-tick equity curve. Added a performance directory that calculates drawdown statistics. Modified the output.py script to use Seaborn and output the equity curve, returns and drawdown curve.
This commit is contained in:
+58
-13
@@ -7,6 +7,7 @@ import os
|
||||
import pandas as pd
|
||||
|
||||
from qsforex.event.event import OrderEvent
|
||||
from qsforex.performance.performance import create_drawdowns
|
||||
from qsforex.portfolio.position import Position
|
||||
from qsforex.settings import OUTPUT_RESULTS_DIR
|
||||
|
||||
@@ -14,7 +15,8 @@ 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")
|
||||
equity=Decimal("100000.00"), risk_per_trade=Decimal("0.02"),
|
||||
backtest=True
|
||||
):
|
||||
self.ticker = ticker
|
||||
self.events = events
|
||||
@@ -23,9 +25,10 @@ class Portfolio(object):
|
||||
self.equity = equity
|
||||
self.balance = deepcopy(self.equity)
|
||||
self.risk_per_trade = risk_per_trade
|
||||
self.backtest = backtest
|
||||
self.trade_units = self.calc_risk_position_size()
|
||||
self.positions = {}
|
||||
self.equity = []
|
||||
self.backtest_file = self.create_equity_file()
|
||||
|
||||
def calc_risk_position_size(self):
|
||||
return self.equity * self.risk_per_trade
|
||||
@@ -66,16 +69,61 @@ class Portfolio(object):
|
||||
del[self.positions[currency_pair]]
|
||||
return True
|
||||
|
||||
def append_equity_row(self, time, balance):
|
||||
d = {"time": time, "balance": balance}
|
||||
self.equity.append(d)
|
||||
def create_equity_file(self):
|
||||
filename = "backtest.csv"
|
||||
out_file = open(os.path.join(OUTPUT_RESULTS_DIR, filename), "w")
|
||||
header = "Timestamp,Balance"
|
||||
for pair in self.ticker.pairs:
|
||||
header += ",%s" % pair
|
||||
header += "\n"
|
||||
out_file.write(header)
|
||||
if self.backtest:
|
||||
print(header[:-2])
|
||||
return out_file
|
||||
|
||||
def output_results(self):
|
||||
filename = "equity.csv"
|
||||
out_file = os.path.join(OUTPUT_RESULTS_DIR, filename)
|
||||
df_equity = pd.DataFrame.from_records(self.equity, index='time')
|
||||
df_equity.to_csv(out_file)
|
||||
print("Simulation complete and results exported to %s" % filename)
|
||||
# Closes off the Backtest.csv file so it can be
|
||||
# read via Pandas without problems
|
||||
self.backtest_file.close()
|
||||
|
||||
in_filename = "backtest.csv"
|
||||
out_filename = "equity.csv"
|
||||
in_file = os.path.join(OUTPUT_RESULTS_DIR, in_filename)
|
||||
out_file = os.path.join(OUTPUT_RESULTS_DIR, out_filename)
|
||||
|
||||
# Create equity curve dataframe
|
||||
df = pd.read_csv(in_file, index_col=0)
|
||||
df.dropna(inplace=True)
|
||||
df["Total"] = df.sum(axis=1)
|
||||
df["Returns"] = df["Total"].pct_change()
|
||||
df["Equity"] = (1.0+df["Returns"]).cumprod()
|
||||
|
||||
# Create drawdown statistics
|
||||
drawdown, max_dd, dd_duration = create_drawdowns(df["Equity"])
|
||||
df["Drawdown"] = drawdown
|
||||
df.to_csv(out_file, index=True)
|
||||
|
||||
print("Simulation complete and results exported to %s" % out_filename)
|
||||
|
||||
def update_portfolio(self, tick_event):
|
||||
"""
|
||||
This updates all positions ensuring an up to date
|
||||
unrealised profit and loss (PnL).
|
||||
"""
|
||||
currency_pair = tick_event.instrument
|
||||
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:
|
||||
print(out_line[:-2])
|
||||
self.backtest_file.write(out_line)
|
||||
|
||||
def execute_signal(self, signal_event):
|
||||
side = signal_event.side
|
||||
@@ -124,7 +172,4 @@ class Portfolio(object):
|
||||
|
||||
order = OrderEvent(currency_pair, units, "market", side)
|
||||
self.events.put(order)
|
||||
|
||||
print("Balance: %0.2f" % self.balance)
|
||||
self.append_equity_row(time, self.balance)
|
||||
|
||||
@@ -11,6 +11,7 @@ class TickerMock(object):
|
||||
"""
|
||||
|
||||
def __init__(self):
|
||||
self.pairs = ["GBPUSD", "EURUSD"]
|
||||
self.prices = {
|
||||
"GBPUSD": {"bid": Decimal("1.50328"), "ask": Decimal("1.50349")},
|
||||
"USDGBP": {"bid": Decimal("0.66521"), "ask": Decimal("0.66512")},
|
||||
@@ -18,6 +19,7 @@ class TickerMock(object):
|
||||
}
|
||||
|
||||
|
||||
|
||||
# =====================================
|
||||
# GBP Home Currency with GBP/USD traded
|
||||
# =====================================
|
||||
|
||||
Reference in New Issue
Block a user