Skip to content

Commit f452e76

Browse files
committed
Fix pretty print backtest
1 parent db0e220 commit f452e76

File tree

5 files changed

+622
-41
lines changed

5 files changed

+622
-41
lines changed

investing_algorithm_framework/domain/models/trade/trade.py

Lines changed: 24 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
from dateutil.parser import parse
2+
13
from investing_algorithm_framework.domain.models.base_model import BaseModel
24
from investing_algorithm_framework.domain.models.order import OrderSide
35
from investing_algorithm_framework.domain.models.trade.trade_status import \
@@ -154,7 +156,9 @@ def net_gain_absolute(self):
154156
def net_gain_percentage(self):
155157

156158
if TradeStatus.CLOSED.equals(self.status):
157-
return (self.net_gain / self.cost) * 100
159+
160+
if self.cost != 0:
161+
return (self.net_gain / self.cost) * 100
158162

159163
else:
160164
gain = 0
@@ -170,7 +174,7 @@ def net_gain_percentage(self):
170174
if self.cost != 0:
171175
return (gain / self.cost) * 100
172176

173-
return 0
177+
return 0
174178

175179
@property
176180
def percentage_change(self):
@@ -254,26 +258,40 @@ def to_dict(self, datetime_format=None):
254258
"opened_at": opened_at,
255259
"closed_at": closed_at,
256260
"updated_at": updated_at,
257-
"net_gain": self.net_gain
261+
"net_gain": self.net_gain,
262+
"cost": self.cost,
258263
}
259264

260265
@staticmethod
261266
def from_dict(data):
267+
opened_at = None
268+
closed_at = None
269+
updated_at = None
270+
271+
if "opened_at" in data and data["opened_at"] is not None:
272+
opened_at = parse(data["opened_at"])
273+
274+
if "closed_at" in data and data["closed_at"] is not None:
275+
closed_at = parse(data["closed_at"])
276+
277+
if "updated_at" in data and data["updated_at"] is not None:
278+
updated_at = parse(data["updated_at"])
279+
262280
return Trade(
263281
id=data.get("id", None),
264282
orders=data.get("orders", None),
265283
target_symbol=data["target_symbol"],
266284
trading_symbol=data["trading_symbol"],
267285
amount=data["amount"],
268286
open_price=data["open_price"],
269-
opened_at=data["opened_at"],
270-
closed_at=data["closed_at"],
287+
opened_at=opened_at,
288+
closed_at=closed_at,
271289
remaining=data.get("remaining", 0),
272290
net_gain=data.get("net_gain", 0),
273291
last_reported_price=data.get("last_reported_price"),
274292
status=data["status"],
275293
cost=data.get("cost", 0),
276-
updated_at=data.get("updated_at"),
294+
updated_at=updated_at,
277295
)
278296

279297
def __repr__(self):

investing_algorithm_framework/domain/utils/backtesting.py

Lines changed: 35 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -43,22 +43,22 @@ def pretty_print_profit_evaluation(reports, precision=4):
4343
report.name for report in reports
4444
]
4545
profit_table["Profit"] = [
46-
f"{report.total_net_gain:.{precision}f} {report.trading_symbol}"
46+
f"{float(report.total_net_gain):.{precision}f} {report.trading_symbol}"
4747
for report in reports
4848
]
4949
profit_table["Profit percentage"] = [
50-
f"{report.total_net_gain_percentage:.{precision}f}%" for report in reports
50+
f"{float(report.total_net_gain_percentage):.{precision}f}%" for report in reports
5151
]
5252
profit_table["Percentage positive trades"] = [
53-
f"{report.percentage_positive_trades:.{0}f}%"
53+
f"{float(report.percentage_positive_trades):.{0}f}%"
5454
for report in reports
5555
]
5656
profit_table["Date range"] = [
5757
f"{report.backtest_date_range.name} {report.backtest_date_range.start_date} - {report.backtest_date_range.end_date}"
5858
for report in reports
5959
]
6060
profit_table["Total value"] = [
61-
f"{report.total_value:.{precision}f}" for report in reports
61+
f"{float(report.total_value):.{precision}f}" for report in reports
6262
]
6363
print(tabulate(profit_table, headers="keys", tablefmt="rounded_grid"))
6464

@@ -69,21 +69,21 @@ def pretty_print_growth_evaluation(reports, precision=4):
6969
report.name for report in reports
7070
]
7171
growth_table["Growth"] = [
72-
f"{report.growth:.{precision}f} {report.trading_symbol}" for report in reports
72+
f"{float(report.growth):.{precision}f} {report.trading_symbol}" for report in reports
7373
]
7474
growth_table["Growth percentage"] = [
75-
f"{report.growth_rate:.{precision}f}%" for report in reports
75+
f"{float(report.growth_rate):.{precision}f}%" for report in reports
7676
]
7777
growth_table["Percentage positive trades"] = [
78-
f"{report.percentage_positive_trades:.{0}f}%"
78+
f"{float(report.percentage_positive_trades):.{0}f}%"
7979
for report in reports
8080
]
8181
growth_table["Date range"] = [
8282
f"{report.backtest_date_range.name} {report.backtest_date_range.start_date} - {report.backtest_date_range.end_date}"
8383
for report in reports
8484
]
8585
growth_table["Total value"] = [
86-
f"{report.total_value:.{precision}f}" for report in reports
86+
f"{float(report.total_value):.{precision}f}" for report in reports
8787
]
8888
print(
8989
tabulate(growth_table, headers="keys", tablefmt="rounded_grid")
@@ -102,7 +102,7 @@ def pretty_print_percentage_positive_trades_evaluation(
102102
report.name for report in order[:number_of_reports]
103103
]
104104
profit_table["Percentage positive trades"] = [
105-
f"{report.percentage_positive_trades:.{precision}f}%"
105+
f"{float(report.percentage_positive_trades):.{precision}f}%"
106106
for report in order[:number_of_reports]
107107
]
108108
profit_table["Total amount of trades"] = [
@@ -118,7 +118,7 @@ def pretty_print_percentage_positive_trades_evaluation(
118118
report.name for report in least
119119
]
120120
table["Percentage positive trades"] = [
121-
f"{report.percentage_positive_trades:.{precision}f}%"
121+
f"{float(report.percentage_positive_trades):.{precision}f}%"
122122
for report in least
123123
]
124124
table["Total amount of trades"] = [
@@ -177,7 +177,7 @@ def pretty_print_price_efficiency(reports, precision=4):
177177
for symbol in price_efficiency:
178178
row = {}
179179
row["Symbol"] = symbol
180-
row["Efficiency ratio / Noise"] = f"{price_efficiency[symbol]:.{precision}f}"
180+
row["Efficiency ratio / Noise"] = f"{float(price_efficiency[symbol]):.{precision}f}"
181181
row["Date"] = f"{report.backtest_start_date} - {report.backtest_end_date}"
182182

183183

@@ -228,7 +228,7 @@ def pretty_print_most_profitable(
228228
):
229229
profits = evaluation.get_profit_order(backtest_date_range=backtest_date_range)
230230
profit = profits[0]
231-
print(f"{COLOR_YELLOW}Most profitable:{COLOR_RESET} {COLOR_GREEN}Algorithm {profit.name} {profit.total_net_gain:.{precision}f} {profit.trading_symbol} {profit.total_net_gain_percentage:.{precision}f}%{COLOR_RESET}")
231+
print(f"{COLOR_YELLOW}Most profitable:{COLOR_RESET} {COLOR_GREEN}Algorithm {profit.name} {float(profit.total_net_gain):.{precision}f} {profit.trading_symbol} {float(profit.total_net_gain_percentage):.{precision}f}%{COLOR_RESET}")
232232

233233

234234
def pretty_print_most_growth(
@@ -238,7 +238,7 @@ def pretty_print_most_growth(
238238
):
239239
profits = evaluation.get_growth_order(backtest_date_range=backtest_date_range)
240240
profit = profits[0]
241-
print(f"{COLOR_YELLOW}Most growth:{COLOR_RESET} {COLOR_GREEN}Algorithm {profit.name} {profit.growth:.{precision}f} {profit.trading_symbol} {profit.growth_rate:.{precision}f}%{COLOR_RESET}")
241+
print(f"{COLOR_YELLOW}Most growth:{COLOR_RESET} {COLOR_GREEN}Algorithm {profit.name} {float(profit.growth):.{precision}f} {profit.trading_symbol} {float(profit.growth_rate):.{precision}f}%{COLOR_RESET}")
242242

243243

244244
def pretty_print_percentage_positive_trades(
@@ -248,7 +248,7 @@ def pretty_print_percentage_positive_trades(
248248
):
249249
percentages = evaluation.get_percentage_positive_trades_order(backtest_date_range=backtest_date_range)
250250
percentages = percentages[0]
251-
print(f"{COLOR_YELLOW}Most positive trades:{COLOR_RESET} {COLOR_GREEN}Algorithm {percentages.name} {percentages.percentage_positive_trades:.{precision}f}%{COLOR_RESET}")
251+
print(f"{COLOR_YELLOW}Most positive trades:{COLOR_RESET} {COLOR_GREEN}Algorithm {percentages.name} {float(percentages.percentage_positive_trades):.{precision}f}%{COLOR_RESET}")
252252

253253

254254
def pretty_print_trades(backtest_report, precision=4):
@@ -268,14 +268,14 @@ def pretty_print_trades(backtest_report, precision=4):
268268
trade.duration for trade in backtest_report.trades
269269
]
270270
trades_table[f"Size ({backtest_report.trading_symbol})"] = [
271-
f"{trade.size:.{precision}f}" for trade in backtest_report.trades
271+
f"{float(trade.size):.{precision}f}" for trade in backtest_report.trades
272272
]
273273
trades_table[f"Net gain ({backtest_report.trading_symbol})"] = [
274-
f"{trade.net_gain:.{precision}f}" + (" (unrealized)" if trade.closed_price is None else "")
274+
f"{float(trade.net_gain):.{precision}f}" + (" (unrealized)" if trade.closed_price is None else "")
275275
for trade in backtest_report.trades
276276
]
277277
trades_table["Net gain percentage"] = [
278-
f"{trade.net_gain_percentage:.{precision}f}%" + (" (unrealized)" if trade.closed_price is None else "")
278+
f"{float(trade.net_gain_percentage):.{precision}f}%" + (" (unrealized)" if trade.closed_price is None else "")
279279
for trade in backtest_report.trades
280280
]
281281
trades_table[f"Open price ({backtest_report.trading_symbol})"] = [
@@ -312,8 +312,8 @@ def pretty_print_backtest_reports_evaluation(
312312
:%%%#+- .=*#%%% {COLOR_GREEN}Backtest reports evaluation{COLOR_RESET}
313313
*%%%%%%%+------=*%%%%%%%- {COLOR_GREEN}---------------------------{COLOR_RESET}
314314
*%%%%%%%%%%%%%%%%%%%%%%%- {COLOR_YELLOW}Number of reports:{COLOR_RESET} {COLOR_GREEN}{number_of_backtest_reports} backtest reports{COLOR_RESET}
315-
.%%%%%%%%%%%%%%%%%%%%%%# {COLOR_YELLOW}Largest overall profit:{COLOR_RESET}{COLOR_GREEN}{COLOR_RESET}{COLOR_GREEN} (Algorithm {most_profitable.name}) {most_profitable.total_net_gain:.{precision}f} {most_profitable.trading_symbol} {most_profitable.total_net_gain_percentage:.{precision}f}% ({most_profitable.backtest_date_range.name} {most_profitable.backtest_date_range.start_date} - {most_profitable.backtest_date_range.end_date}){COLOR_RESET}
316-
#%%%####%%%%%%%%**#%%%+ {COLOR_YELLOW}Largest overall growth:{COLOR_RESET}{COLOR_GREEN} (Algorithm {most_profitable.name}) {most_growth.growth:.{precision}f} {most_growth.trading_symbol} {most_growth.growth_rate:.{precision}f}% ({most_growth.backtest_date_range.name} {most_growth.backtest_date_range.start_date} - {most_growth.backtest_date_range.end_date}){COLOR_RESET}
315+
.%%%%%%%%%%%%%%%%%%%%%%# {COLOR_YELLOW}Largest overall profit:{COLOR_RESET}{COLOR_GREEN}{COLOR_RESET}{COLOR_GREEN} (Algorithm {most_profitable.name}) {float(most_profitable.total_net_gain):.{precision}f} {most_profitable.trading_symbol} {float(most_profitable.total_net_gain_percentage):.{precision}f}% ({most_profitable.backtest_date_range.name} {most_profitable.backtest_date_range.start_date} - {most_profitable.backtest_date_range.end_date}){COLOR_RESET}
316+
#%%%####%%%%%%%%**#%%%+ {COLOR_YELLOW}Largest overall growth:{COLOR_RESET}{COLOR_GREEN} (Algorithm {most_profitable.name}) {float(most_growth.growth):.{precision}f} {most_growth.trading_symbol} {float(most_growth.growth_rate):.{precision}f}% ({most_growth.backtest_date_range.name} {most_growth.backtest_date_range.start_date} - {most_growth.backtest_date_range.end_date}){COLOR_RESET}
317317
.:-+*%%%%- {COLOR_PURPLE}-+..#{COLOR_RESET}%%%+.{COLOR_PURPLE}+- +{COLOR_RESET}%%%#*=-:
318318
.:-=*%%%%. {COLOR_PURPLE}+={COLOR_RESET} .%%# {COLOR_PURPLE}-+.-{COLOR_RESET}%%%%=-:..
319319
.:=+#%%%%%*###%%%%#*+#%%%%%%*+-:
@@ -381,14 +381,14 @@ def pretty_print_backtest(
381381
.:-+*%%%%- {COLOR_PURPLE}-+..#{COLOR_RESET}%%%+.{COLOR_PURPLE}+- +{COLOR_RESET}%%%#*=-: {COLOR_YELLOW}Number of runs:{COLOR_RESET}{COLOR_GREEN} {backtest_report.number_of_runs}{COLOR_RESET}
382382
.:-=*%%%%. {COLOR_PURPLE}+={COLOR_RESET} .%%# {COLOR_PURPLE}-+.-{COLOR_RESET}%%%%=-:.. {COLOR_YELLOW}Number of orders:{COLOR_RESET}{COLOR_GREEN} {backtest_report.number_of_orders}{COLOR_RESET}
383383
.:=+#%%%%%*###%%%%#*+#%%%%%%*+-: {COLOR_YELLOW}Initial balance:{COLOR_RESET}{COLOR_GREEN} {backtest_report.initial_unallocated}{COLOR_RESET}
384-
+%%%%%%%%%%%%%%%%%%%= {COLOR_YELLOW}Final balance:{COLOR_RESET}{COLOR_GREEN} {backtest_report.total_value:.{precision}f}{COLOR_RESET}
385-
:++ .=#%%%%%%%%%%%%%*- {COLOR_YELLOW}Total net gain:{COLOR_RESET}{COLOR_GREEN} {backtest_report.total_net_gain:.{precision}f} {backtest_report.total_net_gain_percentage:.{precision}}%{COLOR_RESET}
386-
:++: :+%%%%%%#-. {COLOR_YELLOW}Growth:{COLOR_RESET}{COLOR_GREEN} {backtest_report.growth:.{precision}f} {backtest_report.growth_rate:.{precision}}%{COLOR_RESET}
384+
+%%%%%%%%%%%%%%%%%%%= {COLOR_YELLOW}Final balance:{COLOR_RESET}{COLOR_GREEN} {float(backtest_report.total_value):.{precision}f}{COLOR_RESET}
385+
:++ .=#%%%%%%%%%%%%%*- {COLOR_YELLOW}Total net gain:{COLOR_RESET}{COLOR_GREEN} {float(backtest_report.total_net_gain):.{precision}f} {float(backtest_report.total_net_gain_percentage):.{precision}}%{COLOR_RESET}
386+
:++: :+%%%%%%#-. {COLOR_YELLOW}Growth:{COLOR_RESET}{COLOR_GREEN} {float(backtest_report.growth):.{precision}f} {float(backtest_report.growth_rate):.{precision}}%{COLOR_RESET}
387387
:++: .%%%%%#= {COLOR_YELLOW}Number of trades closed:{COLOR_RESET}{COLOR_GREEN} {backtest_report.number_of_trades_closed}{COLOR_RESET}
388388
:++: .#%%%%%#*= {COLOR_YELLOW}Number of trades open(end of backtest):{COLOR_RESET}{COLOR_GREEN} {backtest_report.number_of_trades_open}{COLOR_RESET}
389389
:++- :%%%%%%%%%+= {COLOR_YELLOW}Percentage positive trades:{COLOR_RESET}{COLOR_GREEN} {backtest_report.percentage_positive_trades}%{COLOR_RESET}
390390
.++- -%%%%%%%%%%%+= {COLOR_YELLOW}Percentage negative trades:{COLOR_RESET}{COLOR_GREEN} {backtest_report.percentage_negative_trades}%{COLOR_RESET}
391-
.++- .%%%%%%%%%%%%%+= {COLOR_YELLOW}Average trade size:{COLOR_RESET}{COLOR_GREEN} {backtest_report.average_trade_size:.{precision}f} {backtest_report.trading_symbol}{COLOR_RESET}
391+
.++- .%%%%%%%%%%%%%+= {COLOR_YELLOW}Average trade size:{COLOR_RESET}{COLOR_GREEN} {float(backtest_report.average_trade_size):.{precision}f} {backtest_report.trading_symbol}{COLOR_RESET}
392392
.++- *%%%%%%%%%%%%%*+: {COLOR_YELLOW}Average trade duration:{COLOR_RESET}{COLOR_GREEN} {backtest_report.average_trade_duration} hours{COLOR_RESET}
393393
.++- %%%%%%%%%%%%%%#+=
394394
=++........:::%%%%%%%%%%%%%%*+-
@@ -405,35 +405,35 @@ def pretty_print_backtest(
405405
position.symbol for position in backtest_report.positions
406406
]
407407
position_table["Amount"] = [
408-
f"{position.amount:.{precision}f}" for position in
408+
f"{float(position.amount):.{precision}f}" for position in
409409
backtest_report.positions
410410
]
411411
position_table["Pending buy amount"] = [
412-
f"{position.amount_pending_buy:.{precision}f}"
412+
f"{float(position.amount_pending_buy):.{precision}f}"
413413
for position in backtest_report.positions
414414
]
415415
position_table["Pending sell amount"] = [
416-
f"{position.amount_pending_sell:.{precision}f}"
416+
f"{float(position.amount_pending_sell):.{precision}f}"
417417
for position in backtest_report.positions
418418
]
419419
position_table[f"Cost ({backtest_report.trading_symbol})"] = [
420-
f"{position.cost:.{precision}f}"
420+
f"{float(position.cost):.{precision}f}"
421421
for position in backtest_report.positions
422422
]
423423
position_table[f"Value ({backtest_report.trading_symbol})"] = [
424-
f"{position.value:.{precision}f}"
424+
f"{float(position.value):.{precision}f}"
425425
for position in backtest_report.positions
426426
]
427427
position_table["Percentage of portfolio"] = [
428-
f"{position.percentage_of_portfolio:.{precision}f}%"
428+
f"{float(position.percentage_of_portfolio):.{precision}f}%"
429429
for position in backtest_report.positions
430430
]
431431
position_table[f"Growth ({backtest_report.trading_symbol})"] = [
432-
f"{position.growth:.{precision}f}"
432+
f"{float(position.growth):.{precision}f}"
433433
for position in backtest_report.positions
434434
]
435435
position_table["Growth_rate"] = [
436-
f"{position.growth_rate:.{precision}f}%"
436+
f"{float(position.growth_rate):.{precision}f}%"
437437
for position in backtest_report.positions
438438
]
439439
print(
@@ -457,20 +457,20 @@ def pretty_print_backtest(
457457
trade.duration for trade in backtest_report.trades
458458
]
459459
trades_table[f"Cost ({backtest_report.trading_symbol})"] = [
460-
f"{trade.cost:.{precision}f}" for trade in backtest_report.trades
460+
f"{float(trade.cost):.{precision}f}" for trade in backtest_report.trades
461461
]
462462
trades_table[f"Net gain ({backtest_report.trading_symbol})"] = [
463-
f"{trade.net_gain:.{precision}f}"
463+
f"{float(trade.net_gain):.{precision}f}"
464464
for trade in backtest_report.trades
465465
]
466466

467467
# Add (unrealized) to the net gain if the trade is still open
468468
trades_table[f"Net gain ({backtest_report.trading_symbol})"] = [
469-
f"{trade.net_gain_absolute:.{precision}f}" + (" (unrealized)" if not TradeStatus.CLOSED.equals(trade.status) else "")
469+
f"{float(trade.net_gain_absolute):.{precision}f}" + (" (unrealized)" if not TradeStatus.CLOSED.equals(trade.status) else "")
470470
for trade in backtest_report.trades
471471
]
472472
trades_table["Net gain percentage"] = [
473-
f"{trade.net_gain_percentage:.{precision}f}%" + (" (unrealized)" if not TradeStatus.CLOSED.equals(trade.status) else "")
473+
f"{float(trade.net_gain_percentage):.{precision}f}%" + (" (unrealized)" if not TradeStatus.CLOSED.equals(trade.status) else "")
474474
for trade in backtest_report.trades
475475
]
476476
trades_table[f"Open price ({backtest_report.trading_symbol})"] = [

tests/domain/backtesting/__init__.py

Whitespace-only changes.
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
import os
2+
from unittest import TestCase
3+
4+
from investing_algorithm_framework.domain import pretty_print_backtest, \
5+
load_backtest_report
6+
7+
8+
class Test(TestCase):
9+
10+
def setUp(self):
11+
self.resource_dir = os.path.abspath(
12+
os.path.join(
13+
os.path.join(
14+
os.path.join(
15+
os.path.join(
16+
os.path.realpath(__file__),
17+
os.pardir
18+
),
19+
os.pardir
20+
),
21+
os.pardir
22+
),
23+
"resources"
24+
)
25+
)
26+
27+
def test_pretty_print(self):
28+
path = os.path.join(
29+
self.resource_dir,
30+
"backtest_reports_for_testing/report_GoldenCrossStrategy_backtest-start-date_2023-08-24-00-00_backtest-end-date_2023-12-02-00-00_created-at_2025-01-26-23-24.json"
31+
)
32+
report = load_backtest_report(path)
33+
pretty_print_backtest(report)

0 commit comments

Comments
 (0)