Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feat/backend charts upgrade #122

Closed
wants to merge 35 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
35 commits
Select commit Hold shift + click to select a range
c9aa0ef
(feat) add data_viz initial package
tomasgaudino Jan 11, 2024
36d0c5d
(feat) add data viz dtypes + add pandas_ta and positions tracers
tomasgaudino Jan 11, 2024
7ddb6ee
(feat) add candles base class
tomasgaudino Jan 11, 2024
93b18dc
(feat) add backtesting candles base class
tomasgaudino Jan 11, 2024
7ff9536
(refactor) change pnl_vs_maxdrawdown logic
tomasgaudino Jan 11, 2024
278b9d0
(feat) replace pnl_over_time fig + implement backtesting candles base…
tomasgaudino Jan 11, 2024
9a3bba8
(feat) remove viz management from strategy analysis + fix pnl_over_time
tomasgaudino Jan 11, 2024
774dd4e
(refactor) rename class
tomasgaudino Jan 11, 2024
2a05277
(feat) add standard colors to tracers.py
tomasgaudino Jan 11, 2024
9689300
(refactor) improve tracers methods names and add some docs
tomasgaudino Jan 11, 2024
28850f1
(feat) add realized pnl over trading pair, realized pnl over time and…
tomasgaudino Jan 11, 2024
a4393c1
(refactor) add some doc to candles base
tomasgaudino Jan 11, 2024
01d1a2e
(feat) add charts base class
tomasgaudino Jan 11, 2024
f29684a
(feat) add backtesting and performance chart classes
tomasgaudino Jan 11, 2024
6799135
(feat) implement performance chart class in strategy performance page
tomasgaudino Jan 11, 2024
ffb18cd
(feat) implement backtesting candles and charts in analyze page
tomasgaudino Jan 11, 2024
ee60577
(feat) remove deprecated methods from graphs.py
tomasgaudino Jan 11, 2024
0786a48
(refactor) remove unused imports from graphs.py
tomasgaudino Jan 11, 2024
582e1d0
(feat) standardize intraday performance chart and implement in strate…
tomasgaudino Jan 11, 2024
79af60b
(refactor) rename attribute names
tomasgaudino Jan 11, 2024
bb38651
(refactor) standardize returns histogram and implement in strategy pe…
tomasgaudino Jan 11, 2024
8b004cb
(refactor) standardize positions sunburst and implement in strategy p…
tomasgaudino Jan 12, 2024
d04e939
(fix) fix class declaration
tomasgaudino Jan 12, 2024
b405cff
(refactor) simplify buys and sells traces generation + remove unused …
tomasgaudino Jan 12, 2024
cf5c370
(feat) add performance candles class + set datetime as index in some …
tomasgaudino Jan 12, 2024
b703cff
(refactor) refactor add buy and sell trades properties + fix buys and…
tomasgaudino Jan 12, 2024
f54f931
(feat) add composed pnl to performance candles class + update_layout …
tomasgaudino Jan 12, 2024
87cd651
(feat) add quote inventory change to performance candles class + remo…
tomasgaudino Jan 12, 2024
b70d880
(fix) minor fix
tomasgaudino Jan 12, 2024
c6987ad
(feat) replace PlotlyIndicatorsConfigBase by IndicatorConfig list + c…
tomasgaudino Mar 9, 2024
ce98e4b
(feat) add annotations, main and max height + improve rows handling +…
tomasgaudino Mar 22, 2024
03c843a
(fix) fix side choosing for backtesting candles
tomasgaudino Mar 22, 2024
192155c
(feat) add v2 executors table + get_entry_traces method + add dca pri…
tomasgaudino Mar 22, 2024
87c5664
(wip) improve new executors parsing
tomasgaudino Mar 22, 2024
e6c26bb
(fix) fix show_dca_prices declaration + remove reserved word param fr…
tomasgaudino Mar 22, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Empty file added data_viz/__init__.py
Empty file.
Empty file.
65 changes: 65 additions & 0 deletions data_viz/backtesting/backtesting_candles.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
from data_viz.candles import CandlesBase
import pandas as pd
from typing import List
from data_viz.dtypes import IndicatorConfig
from quants_lab.strategy.strategy_analysis import StrategyAnalysis


class BacktestingCandles(CandlesBase):
def __init__(self,
strategy_analysis: StrategyAnalysis,
indicators_config: List[IndicatorConfig] = None,
line_mode: bool = False,
show_buys: bool = True,
show_sells: bool = True,
show_positions: bool = True,
show_indicators: bool = False):
self.candles_df = strategy_analysis.candles_df
super().__init__(candles_df=self.candles_df,
indicators_config=indicators_config,
line_mode=line_mode,
show_indicators=show_indicators)
self.positions = strategy_analysis.positions
if show_buys:
self.add_buy_trades(data=self.buys)
if show_sells:
self.add_sell_trades(data=self.sells)
if show_positions:
self.add_positions()

def force_datetime_format(self):
datetime_columns = ["timestamp", "close_time", "tl", "stop_loss_time", "take_profit_time"]
for col in datetime_columns:
self.positions[col] = pd.to_datetime(self.positions[col], unit="ms")

@property
def buys(self):
df = self.positions[["timestamp", "close", "close_price", "close_time", "side"]].copy()
df["price"] = df.apply(lambda row: row["close"] if row["side"] == "BUY" else row["close_price"], axis=1)
df["timestamp"] = df.apply(lambda row: row["timestamp"] if row["side"] == "BUY" else row["close_time"], axis=1)
df.set_index("timestamp", inplace=True)
return df["price"]

@property
def sells(self):
df = self.positions[["timestamp", "close", "close_price", "close_time", "side"]].copy()
df["price"] = df.apply(lambda row: row["close"] if row["side"] == "SELL" else row["close_price"], axis=1)
df["timestamp"] = df.apply(lambda row: row["timestamp"] if row["side"] == "SELL" else row["close_time"], axis=1)
df.set_index("timestamp", inplace=True)
return df["price"]

def add_positions(self):
i = 1
for index, rown in self.positions.iterrows():
i += 1
self.base_figure.add_trace(self.tracer.get_positions_traces(position_number=i, open_time=rown["timestamp"],
close_time=rown["close_time"],
open_price=rown["close"],
close_price=rown["close_price"],
side=rown["side"],
close_type=rown["close_type"],
stop_loss=rown["sl"], take_profit=rown["tp"],
time_limit=rown["tl"],
net_pnl_quote=rown["net_pnl_quote"]),
row=1, col=1)

32 changes: 32 additions & 0 deletions data_viz/backtesting/backtesting_charts.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
from typing import Union
import pandas as pd
import plotly.graph_objects as go

from data_viz.charts import ChartsBase
from data_viz.tracers import PerformancePlotlyTracer
from quants_lab.strategy.strategy_analysis import StrategyAnalysis


class BacktestingCharts(ChartsBase):
def __init__(self,
# TODO: Rename StrategyData as RealPerformanceData and StrategyAnalysis as BacktestingAnalysis
source: Union[StrategyAnalysis, None] = None):
super().__init__()
self.source = source
self.tracer = PerformancePlotlyTracer()

@property
def realized_pnl_over_time_fig(self):
if self.source is not None:
data = self.source.positions.copy()
data.sort_values(by="timestamp", inplace=True)
return self.realized_pnl_over_time(data=data,
cum_realized_pnl_column="net_pnl_quote")
else:
return go.Figure()

def pnl_vs_max_drawdown_fig(self, data: pd.DataFrame = None):
return self.pnl_vs_max_drawdown(data=data,
max_drawdown_pct_column="max_drawdown_pct",
net_pnl_pct_column="net_pnl_pct",
hovertext_column="hover_text")
223 changes: 223 additions & 0 deletions data_viz/candles.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,223 @@
import pandas as pd
from plotly.subplots import make_subplots
import pandas_ta as ta # noqa: F401
from typing import List
from data_viz.tracers import PandasTAPlotlyTracer
from data_viz.tracers import PerformancePlotlyTracer
from data_viz.dtypes import IndicatorConfig
import plotly.graph_objs as go


class CandlesBase:
def __init__(self,
candles_df: pd.DataFrame,
indicators_config: List[IndicatorConfig] = None,
show_annotations=True,
line_mode=False,
show_indicators=False,
main_height=0.7,
max_height=1000,
rows: int = None,
row_heights: list = None):
self.candles_df = candles_df
self.show_indicators = show_indicators
self.indicators_config = indicators_config
self.show_annotations = show_annotations
self.indicators_tracer = PandasTAPlotlyTracer(candles_df)
self.tracer = PerformancePlotlyTracer()
self.line_mode = line_mode
self.main_height = main_height
self.max_height = max_height
self.rows = rows
if rows is None:
rows, row_heights = self.get_n_rows_and_heights()
self.rows = rows
specs = [[{"secondary_y": True}]] * self.rows
self.base_figure = make_subplots(rows=self.rows,
cols=1,
shared_xaxes=True,
vertical_spacing=0.005,
row_heights=row_heights,
specs=specs)
if 'timestamp' in candles_df.columns:
candles_df.set_index("timestamp", inplace=True)
self.min_time = candles_df.index.min()
self.max_time = candles_df.index.max()
self.add_candles_graph()
if self.show_indicators and self.indicators_config is not None:
self.add_indicators()
self.update_layout()

def get_n_rows_and_heights(self):
rows = 1
if self.show_indicators and self.indicators_config is not None:
rows = max([config.row for config in self.indicators_config])
complementary_height = 1 - self.main_height
row_heights = [self.main_height] + [complementary_height / (rows - 1)] * (rows - 1) if rows > 1 else [1]
return rows, row_heights

def figure(self):
return self.base_figure

def add_candles_graph(self):
if self.line_mode:
self.base_figure.add_trace(
go.Scatter(x=self.candles_df.index,
y=self.candles_df['close'],
name="Close",
mode='lines',
line=dict(color='blue')),
row=1, col=1,
)
else:
hover_text = []
if self.show_annotations:
for i in range(len(self.candles_df)):
hover_text.append(
f"Open: {self.candles_df['open'][i]} <br>"
f"High: {self.candles_df['high'][i]} <br>"
f"Low: {self.candles_df['low'][i]} <br>"
f"Close: {self.candles_df['close'][i]} <br>"
)
self.base_figure.add_trace(
go.Candlestick(
x=self.candles_df.index,
open=self.candles_df['open'],
high=self.candles_df['high'],
low=self.candles_df['low'],
close=self.candles_df['close'],
name="OHLC",
hoverinfo="text",
hovertext=hover_text
),
row=1, col=1,
)

def add_volume(self):
self.base_figure.add_trace(
go.Bar(
x=self.candles_df.index,
y=self.candles_df['volume'],
name="Volume",
opacity=0.5,
marker=dict(color='lightgreen'),

),
row=2, col=1,
)

def add_buy_trades(self, data: pd.Series):
buy_traces = self.tracer.get_buys_traces(data=data) if not data.empty else None
if bool(buy_traces):
self.base_figure.add_trace(buy_traces,
row=1, col=1)

def add_sell_trades(self, data: pd.Series):
sell_traces = self.tracer.get_sells_traces(data=data) if not data.empty else None
print(f"Sell traces: {sell_traces}")
if bool(sell_traces):
self.base_figure.add_trace(sell_traces,
row=1, col=1)

def add_quote_inventory_change(self, data: pd.DataFrame, quote_inventory_change_column: str, row_number: int = 3):
quote_inventory_change_trace = self.tracer.get_quote_inventory_change(
data=data,
quote_inventory_change_column=quote_inventory_change_column)
if quote_inventory_change_trace:
self.base_figure.add_trace(quote_inventory_change_trace,
row=row_number, col=1)
self.base_figure.update_yaxes(title_text='Quote Inventory Change', row=row_number, col=1)

def add_pnl(self, data: pd.DataFrame, realized_pnl_column: str, fees_column: str, net_realized_pnl_column: str,
row_number: int = 2):
for trace in self.tracer.get_composed_pnl_traces(data=data,
realized_pnl_column=realized_pnl_column,
fees_column=fees_column,
net_realized_pnl_column=net_realized_pnl_column):
self.base_figure.add_trace(trace, row=row_number, col=1)
self.base_figure.update_yaxes(title_text='PNL', row=row_number, col=1)

def add_positions(self):
"""
Depending on whether the data source is backtesting or performance, the name of the columns might change.
"""
pass

def update_layout(self):
self.base_figure.update_layout(
legend=dict(
orientation="h",
x=0.5,
y=1.04,
xanchor="center",
yanchor="bottom"
),
height=self.max_height,
xaxis=dict(rangeslider_visible=False,
range=[self.min_time, self.max_time]),
yaxis=dict(range=[self.candles_df.low.min(), self.candles_df.high.max()]),
hovermode='x unified'
)
self.base_figure.update_yaxes(title_text="Price", row=1, col=1)
self.base_figure.update_xaxes(title_text="Time", row=self.rows, col=1)

# ----------------------------
# INDICATORS METHODS
# ----------------------------

def add_bollinger_bands(self, indicator_config: IndicatorConfig):
if indicator_config.visible:
bbu_trace, bbm_trace, bbl_trace = self.indicators_tracer.get_bollinger_bands_traces(indicator_config)
if all([bbu_trace, bbm_trace, bbl_trace]):
self.base_figure.add_trace(trace=bbu_trace,
row=indicator_config.row,
col=indicator_config.col)
self.base_figure.add_trace(trace=bbm_trace,
row=indicator_config.row,
col=indicator_config.col)
self.base_figure.add_trace(trace=bbl_trace,
row=indicator_config.row,
col=indicator_config.col)

def add_ema(self, indicator_config: IndicatorConfig):
if indicator_config.visible:
ema_trace = self.indicators_tracer.get_ema_traces(indicator_config)
if ema_trace:
self.base_figure.add_trace(trace=ema_trace,
row=indicator_config.row,
col=indicator_config.col)

def add_macd(self, indicator_config: IndicatorConfig):
if indicator_config.visible:
macd_trace, macd_signal_trace, macd_hist_trace = self.indicators_tracer.get_macd_traces(indicator_config)
if all([macd_trace, macd_signal_trace, macd_hist_trace]):
self.base_figure.add_trace(trace=macd_trace,
row=indicator_config.row,
col=indicator_config.col)
self.base_figure.add_trace(trace=macd_signal_trace,
row=indicator_config.row,
col=indicator_config.col)
self.base_figure.add_trace(trace=macd_hist_trace,
row=indicator_config.row,
col=indicator_config.col)

def add_rsi(self, indicator_config: IndicatorConfig):
if indicator_config.visible:
rsi_trace = self.indicators_tracer.get_rsi_traces(indicator_config)
if rsi_trace:
self.base_figure.add_trace(trace=rsi_trace,
row=indicator_config.row,
col=indicator_config.col)

def add_indicators(self):
for indicator in self.indicators_config:
if indicator.title == "bbands":
self.add_bollinger_bands(indicator)
elif indicator.title == "ema":
self.add_ema(indicator)
elif indicator.title == "macd":
self.add_macd(indicator)
elif indicator.title == "rsi":
self.add_rsi(indicator)
else:
raise ValueError(f"{indicator.title} is not a valid indicator. Choose from bbands, ema, macd, rsi")
Loading