Skip to content

Commit 24130c0

Browse files
committed
Fix string formatting in reporting tables
1 parent dbdfd55 commit 24130c0

File tree

6 files changed

+260
-22
lines changed

6 files changed

+260
-22
lines changed
Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
from investing_algorithm_framework import CCXTOHLCVMarketDataSource
2+
3+
from .strategy_v1 import CrossOverStrategyV1
4+
5+
6+
7+
def configure_strategy(
8+
time_unit,
9+
interval,
10+
assets,
11+
time_frame,
12+
fast,
13+
slow,
14+
trend,
15+
stop_loss_percentage,
16+
stop_loss_sell_size,
17+
take_profit_percentage,
18+
take_profit_sell_size
19+
):
20+
data_providers = []
21+
22+
for asset in assets:
23+
data_providers.append(
24+
CCXTOHLCVMarketDataSource(
25+
identifier=f"{asset}-ohlcv-{time_frame.value}",
26+
market="bitvavo",
27+
symbol=asset,
28+
time_frame=time_frame,
29+
window_size=200
30+
)
31+
)
32+
33+
strategy = CrossOverStrategyV1(
34+
time_unit=time_unit,
35+
interval=interval,
36+
symbol_pairs=assets,
37+
market_data_sources=data_providers,
38+
fast=fast,
39+
slow=slow,
40+
trend=trend,
41+
stop_loss_percentage=stop_loss_percentage,
42+
stop_loss_sell_size=stop_loss_sell_size,
43+
take_profit_percentage=take_profit_percentage,
44+
take_profit_sell_size=take_profit_sell_size
45+
)
46+
return strategy
47+
48+
49+
__all__ = [
50+
"CrossOverStrategyV1",
51+
]
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
from investing_algorithm_framework import (
2+
CCXTOHLCVMarketDataSource, CCXTTickerMarketDataSource
3+
)
4+
5+
bitvavo_btc_eur_ohlcv_2h = CCXTOHLCVMarketDataSource(
6+
identifier="BTC/EUR-ohlcv-2h",
7+
market="BINANCE",
8+
symbol="BTC/EUR",
9+
time_frame="2h",
10+
window_size=200
11+
)
12+
bitvavo_dot_eur_ohlcv_2h = CCXTOHLCVMarketDataSource(
13+
identifier="DOT/EUR-ohlch-2h",
14+
market="BINANCE",
15+
symbol="DOT/EUR",
16+
time_frame="2h",
17+
window_size=200
18+
)
19+
bitvavo_dot_eur_ticker = CCXTTickerMarketDataSource(
20+
identifier="DOT/EUR-ticker",
21+
market="BINANCE",
22+
symbol="DOT/EUR",
23+
backtest_time_frame="2h",
24+
)
25+
bitvavo_btc_eur_ticker = CCXTTickerMarketDataSource(
26+
identifier="BTC/EUR-ticker",
27+
market="BINANCE",
28+
symbol="BTC/EUR",
29+
backtest_time_frame="2h",
30+
)
Lines changed: 153 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,153 @@
1+
from pyindicators import ema, is_crossover, is_above, is_below, is_crossunder
2+
from investing_algorithm_framework import TradingStrategy, TimeUnit, Context, \
3+
OrderSide
4+
5+
from .data_sources import bitvavo_btc_eur_ohlcv_2h, bitvavo_btc_eur_ticker
6+
7+
8+
class CrossOverStrategyV1(TradingStrategy):
9+
"""
10+
A simple trading strategy that uses EMA crossovers to generate buy and
11+
sell signals. The strategy uses a 50-period EMA and a 100-period EMA
12+
to detect golden and death crosses. It also uses a 200-period EMA to
13+
determine the overall trend direction. The strategy trades BTC/EUR
14+
on a 2-hour timeframe. The strategy is designed to be used with the
15+
Investing Algorithm Framework and uses the PyIndicators library
16+
to calculate the EMAs and crossover signals.
17+
18+
The strategy uses a trailing stop loss and take profit to manage
19+
risk. The stop loss is set to 5% below the entry price and the
20+
take profit is set to 10% above the entry price. The stop loss and
21+
take profit are both trailing, meaning that they will move up
22+
with the price when the price goes up.
23+
"""
24+
time_unit = TimeUnit.HOUR
25+
interval = 2
26+
symbol_pairs = ["BTC/EUR"]
27+
market_data_sources = [bitvavo_btc_eur_ohlcv_2h, bitvavo_btc_eur_ticker]
28+
fast = 50
29+
slow = 100
30+
trend = 200
31+
stop_loss_percentage = 2
32+
stop_loss_sell_size = 50
33+
take_profit_percentage = 8
34+
take_profit_sell_size = 50
35+
36+
def __init__(
37+
self,
38+
time_unit: TimeUnit = TimeUnit.HOUR,
39+
interval: int = 2,
40+
symbol_pairs: list[str] = None,
41+
market_data_sources=None,
42+
fast: int = 50,
43+
slow: int = 100,
44+
trend: int = 200,
45+
stop_loss_percentage: float = 2.0,
46+
stop_loss_sell_size: float = 50.0,
47+
take_profit_percentage: float = 8.0,
48+
take_profit_sell_size: float = 50.0
49+
):
50+
super().__init__(
51+
time_unit=time_unit,
52+
interval=interval,
53+
market_data_sources=market_data_sources or self.market_data_sources
54+
)
55+
self.symbol_pairs = symbol_pairs or self.symbol_pairs
56+
self.fast = fast
57+
self.slow = slow
58+
self.trend = trend
59+
self.stop_loss_percentage = stop_loss_percentage
60+
self.stop_loss_sell_size = stop_loss_sell_size
61+
self.take_profit_percentage = take_profit_percentage
62+
self.take_profit_sell_size = take_profit_sell_size
63+
64+
def apply_strategy(self, context: Context, market_data):
65+
66+
for pair in self.symbol_pairs:
67+
symbol = pair.split('/')[0]
68+
69+
# Don't trade if there are open orders for the symbol
70+
# This is important to avoid placing new orders while there are
71+
# existing orders that are not yet filled
72+
if context.has_open_orders(symbol):
73+
continue
74+
75+
ohlcv_data = market_data[f"{pair}-ohlcv-2h"]
76+
# ticker_data = market_data[f"{symbol}-ticker"]
77+
# Add fast, slow, and trend EMAs to the data
78+
ohlcv_data = ema(
79+
ohlcv_data,
80+
source_column="Close",
81+
period=self.fast,
82+
result_column=f"ema_{self.fast}"
83+
)
84+
ohlcv_data = ema(
85+
ohlcv_data,
86+
source_column="Close",
87+
period=self.slow,
88+
result_column=f"ema_{self.slow}"
89+
)
90+
ohlcv_data = ema(
91+
ohlcv_data,
92+
source_column="Close",
93+
period=self.trend,
94+
result_column=f"ema_{self.trend}"
95+
)
96+
97+
price = ohlcv_data["Close"][-1]
98+
99+
if not context.has_position(symbol) \
100+
and self._is_buy_signal(ohlcv_data):
101+
order = context.create_limit_order(
102+
target_symbol=symbol,
103+
order_side=OrderSide.BUY,
104+
price=price,
105+
percentage_of_portfolio=25,
106+
precision=4,
107+
)
108+
trade = context.get_trade(order_id=order.id)
109+
context.add_stop_loss(
110+
trade=trade,
111+
trade_risk_type="trailing",
112+
percentage=self.stop_loss_percentage,
113+
sell_percentage=self.stop_loss_sell_size
114+
)
115+
context.add_take_profit(
116+
trade=trade,
117+
percentage=self.take_profit_percentage,
118+
trade_risk_type="trailing",
119+
sell_percentage=self.take_profit_sell_size
120+
)
121+
122+
if context.has_position(symbol) \
123+
and self._is_sell_signal(ohlcv_data):
124+
open_trades = context.get_open_trades(
125+
target_symbol=symbol
126+
)
127+
128+
for trade in open_trades:
129+
context.close_trade(trade)
130+
131+
def _is_sell_signal(self, data):
132+
return is_crossunder(
133+
data,
134+
first_column=f"ema_{self.fast}",
135+
second_column=f"ema_{self.slow}",
136+
number_of_data_points=2
137+
) and is_below(
138+
data,
139+
first_column=f"ema_{self.fast}",
140+
second_column=f"ema_{self.trend}",
141+
)
142+
143+
def _is_buy_signal(self, data):
144+
return is_crossover(
145+
data=data,
146+
first_column=f"ema_{self.fast}",
147+
second_column=f"ema_{self.slow}",
148+
number_of_data_points=2
149+
) and is_above(
150+
data=data,
151+
first_column=f"ema_{self.fast}",
152+
second_column=f"ema_{self.trend}",
153+
)

investing_algorithm_framework/app/reporting/tables/key_metrics_table.py

Lines changed: 12 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -150,17 +150,19 @@ def highlight_max_drawdown(row):
150150

151151
def create_html_key_metrics_table(results, report):
152152
copy_results = results.to_dict().copy()
153+
format_str = "{:.2f}"
154+
153155
# Format some values to percentages and floats
154-
copy_results['Total Return'] = safe_format_percentage(copy_results['total_return'], "{:.2f}%")
155-
copy_results['CAGR'] = safe_format_percentage(copy_results['cagr'], "{:.2f}%")
156-
copy_results['Sharpe Ratio'] = safe_format(copy_results['sharpe_ratio'], "{:.2f}")
157-
copy_results['Sortino Ratio'] = safe_format(copy_results['sortino_ratio'], "{:.2f}")
158-
copy_results['Profit Factor'] = safe_format(copy_results['profit_factor'], "{:.2f}")
159-
copy_results['Calmar Ratio'] = safe_format(copy_results['calmar_ratio'], "{:.2f}")
160-
copy_results['Annual Volatility'] = safe_format_percentage(copy_results['annual_volatility'], "{:.2f}%")
161-
copy_results['Max Drawdown'] = safe_format_percentage(copy_results['max_drawdown'], "{:.2f}%")
162-
copy_results['Max Drawdown Absolute'] = safe_format(copy_results['max_drawdown_absolute'], "{:.2f} " + report.trading_symbol)
163-
copy_results['Max Daily Drawdown'] = safe_format_percentage(copy_results['max_daily_drawdown'], "{:.2f}%")
156+
copy_results['Total Return'] = f"{safe_format_percentage(copy_results['total_return'], format_str)}%"
157+
copy_results['CAGR'] = f"{safe_format_percentage(copy_results['cagr'],format_str)}%"
158+
copy_results['Sharpe Ratio'] = safe_format(copy_results['sharpe_ratio'], format_str)
159+
copy_results['Sortino Ratio'] = safe_format(copy_results['sortino_ratio'], format_str)
160+
copy_results['Profit Factor'] = safe_format(copy_results['profit_factor'], format_str)
161+
copy_results['Calmar Ratio'] = safe_format(copy_results['calmar_ratio'], format_str)
162+
copy_results['Annual Volatility'] = f"{safe_format_percentage(copy_results['annual_volatility'], format_str)}%"
163+
copy_results['Max Drawdown'] = f"{safe_format_percentage(copy_results['max_drawdown'], format_str)}%"
164+
copy_results['Max Drawdown Absolute'] = f"{safe_format(copy_results['max_drawdown_absolute'], format_str)} {report.trading_symbol}"
165+
copy_results['Max Daily Drawdown'] = f"{safe_format_percentage(copy_results['max_daily_drawdown'], format_str)}%"
164166
copy_results['Max Drawdown Duration'] = f"{copy_results['max_drawdown_duration']} hours - {copy_results['max_drawdown_duration'] // 24} days"
165167

166168
stats = {

investing_algorithm_framework/app/reporting/tables/time_metrics_table.py

Lines changed: 8 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -7,14 +7,15 @@ def create_html_time_metrics_table(results, report):
77
copy_results = results.to_dict().copy()
88
start_date = report.backtest_date_range.start_date
99
end_date = report.backtest_date_range.end_date
10+
string_format = "{:.2f}"
1011
# Format dates
1112
copy_results['Start Date'] = safe_format_date(start_date, "%Y-%m-%d %H:%M")
1213
copy_results['End Date'] = safe_format_date(end_date, "%Y-%m-%d %H:%M")
13-
copy_results['Percentage Winning Months'] = safe_format_percentage(copy_results['percentage_winning_months'], "{:.2f}%")
14-
copy_results['Percentage Winning Years'] = safe_format_percentage(copy_results['percentage_winning_years'], "{:.2f}%")
15-
copy_results['Average Monthly Return'] = safe_format_percentage(copy_results['average_monthly_return'], "{:.2f}%")
16-
copy_results['Average Monthly Return (Losing Months)'] = safe_format_percentage(copy_results['average_monthly_return_losing_months'], "{:.2f}%")
17-
copy_results['Average Monthly Return (Winning Months)'] = safe_format_percentage(copy_results['average_monthly_return_winning_months'], "{:.2f}%")
14+
copy_results['Percentage Winning Months'] = f"{safe_format_percentage(copy_results['percentage_winning_months'], string_format)}%"
15+
copy_results['Percentage Winning Years'] = f"{safe_format_percentage(copy_results['percentage_winning_years'], string_format)}%"
16+
copy_results['Average Monthly Return'] = f"{safe_format_percentage(copy_results['average_monthly_return'], string_format)}%"
17+
copy_results['Average Monthly Return (Losing Months)'] = f"{safe_format_percentage(copy_results['average_monthly_return_losing_months'], string_format)}%"
18+
copy_results['Average Monthly Return (Winning Months)'] = f"{safe_format_percentage(copy_results['average_monthly_return_winning_months'], string_format)}%"
1819

1920
if isinstance(copy_results['best_month'], tuple):
2021
percentage = copy_results['best_month'][0]
@@ -34,13 +35,13 @@ def create_html_time_metrics_table(results, report):
3435
percentage = copy_results['best_year'][0]
3536
date = copy_results['best_year'][1]
3637
copy_results['Best Year'] = f"{safe_format_percentage(
37-
percentage, '{:.2f}'
38+
percentage, string_format
3839
)}% {safe_format_date(date, '%b %Y')}"
3940
if isinstance(copy_results['worst_year'], tuple):
4041
percentage = copy_results['worst_year'][0]
4142
date = copy_results['worst_year'][1]
4243
copy_results['Worst Year'] = f"{safe_format_percentage(
43-
percentage, '{:.2f}'
44+
percentage, string_format
4445
)}% {safe_format_date(date, '%b %Y')}"
4546

4647
stats = {

investing_algorithm_framework/app/reporting/tables/trade_metrics_table.py

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -65,9 +65,10 @@ def highlight_win_loss_ratio(row):
6565

6666
def create_html_trade_metrics_table(results, report):
6767
copy_results = results.to_dict().copy()
68-
copy_results['Trades per Year'] = safe_format(copy_results['trades_per_year'], "{:.2f}")
69-
copy_results['Trades per Day'] = safe_format(copy_results['trade_per_day'], "{:.2f}")
70-
copy_results['Exposure Factor'] = safe_format(copy_results['exposure_factor'], "{:.2f}")
68+
string_format = "{:.2f}"
69+
copy_results['Trades per Year'] = safe_format(copy_results['trades_per_year'], string_format)
70+
copy_results['Trades per Day'] = safe_format(copy_results['trade_per_day'], string_format)
71+
copy_results['Exposure Factor'] = safe_format(copy_results['exposure_factor'],string_format)
7172
best_trade = copy_results['best_trade']
7273

7374
if best_trade is None:
@@ -86,8 +87,8 @@ def create_html_trade_metrics_table(results, report):
8687
copy_results['Worst Trade'] = f"{copy_results['worst_trade'].net_gain:.2f} {report.trading_symbol} ({copy_results['worst_trade'].net_gain_percentage:.2f})%"
8788
copy_results['Worst Trade Date'] = copy_results['worst_trade_date'].strftime('%Y-%m-%d')
8889

89-
copy_results['Trades Average Gain'] = f"{safe_format(copy_results['trades_average_gain'][0], "{:.2f}")} {report.trading_symbol} {copy_results['trades_average_gain'][1]:.2f}%"
90-
copy_results['Trades Average Loss'] = f"{safe_format(copy_results['trades_average_loss'][0], "{:.2f}")} {report.trading_symbol} {copy_results['trades_average_loss'][1]:.2f}%"
90+
copy_results['Trades Average Gain'] = f"{safe_format(copy_results['trades_average_gain'][0], string_format)} {report.trading_symbol} {copy_results['trades_average_gain'][1]:.2f}%"
91+
copy_results['Trades Average Loss'] = f"{safe_format(copy_results['trades_average_loss'][0], string_format)} {report.trading_symbol} {copy_results['trades_average_loss'][1]:.2f}%"
9192
copy_results['Average Trade Duration'] = f"{copy_results['average_trade_duration']:.2f} hours"
9293
copy_results['Number of Trades'] = f"{copy_results['number_of_trades']}"
9394
copy_results['Win Rate'] = f"{copy_results['win_rate']:.2f}%"

0 commit comments

Comments
 (0)