From 0d2c42f70170ec3d4633699a9a712e97d8384bc2 Mon Sep 17 00:00:00 2001 From: cardosofede Date: Fri, 21 Apr 2023 17:36:52 -0300 Subject: [PATCH] (feat) add candles graph --- utils/graphs.py | 256 ++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 256 insertions(+) create mode 100644 utils/graphs.py diff --git a/utils/graphs.py b/utils/graphs.py new file mode 100644 index 0000000..b3b22ef --- /dev/null +++ b/utils/graphs.py @@ -0,0 +1,256 @@ +import pandas as pd +from plotly.subplots import make_subplots +import pandas_ta as ta # noqa: F401 +import streamlit as st + +from utils.data_manipulation import StrategyData +import plotly.graph_objs as go + + +class CandlesGraph: + def __init__(self, candles_df: pd.DataFrame, show_volume=True, extra_rows=1): + self.candles_df = candles_df + self.show_volume = show_volume + rows, heights = self.get_n_rows_and_heights(extra_rows) + self.rows = rows + specs = [[{"secondary_y": True}]] * rows + self.base_figure = make_subplots(rows=rows, cols=1, shared_xaxes=True, vertical_spacing=0.05, row_heights=heights, specs=specs) + self.add_candles_graph() + if self.show_volume: + self.add_volume() + self.update_layout() + + def get_n_rows_and_heights(self, extra_rows): + rows = 1 + extra_rows + self.show_volume + row_heights = [0.3] * (extra_rows) + if self.show_volume: + row_heights.insert(0, 0.2) + row_heights.insert(0, 0.8) + return rows, row_heights + + def figure(self): + return self.base_figure + + def add_candles_graph(self): + self.base_figure.add_trace( + go.Candlestick( + x=self.candles_df['datetime'], + open=self.candles_df['open'], + high=self.candles_df['high'], + low=self.candles_df['low'], + close=self.candles_df['close'], + name="OHLC" + ), + row=1, col=1, + ) + + def add_buy_trades(self, orders_data: pd.DataFrame): + self.base_figure.add_trace( + go.Scatter( + x=orders_data['timestamp'], + y=orders_data['price'], + name='Buy Orders', + mode='markers', + marker=dict( + symbol='triangle-up', + color='green', + size=12, + line=dict(color='black', width=1), + opacity=0.7, + )), + row=1, col=1, + ) + + def add_sell_trades(self, orders_data: pd.DataFrame): + self.base_figure.add_trace( + go.Scatter( + x=orders_data['timestamp'], + y=orders_data['price'], + name='Sell Orders', + mode='markers', + marker=dict(symbol='triangle-down', + color='red', + size=12, + line=dict(color='black', width=1), + opacity=0.7,)), + row=1, col=1, + ) + + def add_bollinger_bands(self, length=20, std=2.0, row=1): + df = self.candles_df.copy() + if len(df) < length: + st.warning("Not enough data to calculate Bollinger Bands") + return + df.ta.bbands(length=length, std=std, append=True) + self.base_figure.add_trace( + go.Scatter( + x=df['datetime'], + y=df[f'BBU_{length}_{std}'], + name='Bollinger Bands', + mode='lines', + line=dict(color='blue', width=1)), + row=row, col=1, + ) + self.base_figure.add_trace( + go.Scatter( + x=df['datetime'], + y=df[f'BBM_{length}_{std}'], + name='Bollinger Bands', + mode='lines', + line=dict(color='blue', width=1)), + row=1, col=1, + ) + self.base_figure.add_trace( + go.Scatter( + x=df['datetime'], + y=df[f'BBL_{length}_{std}'], + name='Bollinger Bands', + mode='lines', + line=dict(color='blue', width=1)), + row=1, col=1, + ) + + def add_volume(self): + self.base_figure.add_trace( + go.Bar( + x=self.candles_df['datetime'], + y=self.candles_df['volume'], + name="Volume", + opacity=0.5, + marker=dict(color='lightgreen') + ), + row=2, col=1, + ) + + def add_ema(self, length=20, row=1): + df = self.candles_df.copy() + if len(df) < length: + st.warning("Not enough data to calculate EMA") + return + df.ta.ema(length=length, append=True) + self.base_figure.add_trace( + go.Scatter( + x=df['datetime'], + y=df[f'EMA_{length}'], + name='EMA', + mode='lines', + line=dict(color='yellow', width=1)), + row=row, col=1, + ) + + def add_base_inventory_change(self, strategy_data: StrategyData, row=3): + # Create a list of colors based on the sign of the amount_new column + self.base_figure.add_trace( + go.Bar( + x=strategy_data.trade_fill["timestamp"], + y=strategy_data.trade_fill["net_amount"], + name="Base Inventory Change", + opacity=0.5, + marker=dict(color=["lightgreen" if amount > 0 else "indianred" for amount in strategy_data.trade_fill["net_amount"]]) + ), + row=row, col=1, + ) + merged_df = pd.merge_asof(self.candles_df, strategy_data.trade_fill, left_on="datetime", right_on="timestamp", direction="nearest") + self.base_figure.add_trace( + go.Scatter( + x=merged_df.datetime, + y=merged_df["cum_net_amount"], + name="Cumulative Base Inventory Change", + mode='lines', + line=dict(color='black', width=1)), + row=row, col=1, secondary_y=True + ) + self.base_figure.update_yaxes(title_text='Cum Base Inventory Change', row=3, col=1, secondary_y=True) + self.base_figure.update_yaxes(title_text='Base Inventory Change', row=3, col=1) + + def add_trade_pnl(self, strategy_data: StrategyData, row=4): + merged_df = pd.merge_asof(self.candles_df, strategy_data.trade_fill, left_on="datetime", right_on="timestamp", direction="nearest") + merged_df["trade_pnl_continuos"] = merged_df["unrealized_trade_pnl"] + merged_df["cum_net_amount"] * merged_df["close"] + + self.base_figure.add_trace( + go.Scatter( + x=merged_df.datetime, + y=merged_df["trade_pnl_continuos"], + name="Cumulative Trade PnL Continuos", + mode='lines', + line=dict(color='chocolate', width=2)), + row=row, col=1 + ) + self.base_figure.add_trace( + go.Scatter( + x=merged_df.datetime, + y=merged_df["realized_trade_pnl"], + name="Cumulative Trade PnL by Trade", + mode='lines', + line=dict(color='cornflowerblue', width=2)), + row=row, col=1 + ) + self.base_figure.update_yaxes(title_text='Cum Trade PnL', row=4, col=1) + + def update_layout(self): + self.base_figure.update_layout( + title={ + 'text': "Market activity", + 'y': 0.95, + 'x': 0.5, + 'xanchor': 'center', + 'yanchor': 'top' + }, + legend=dict( + orientation="h", + yanchor="bottom", + y=-0.2, + xanchor="right", + x=1 + ), + height=1000, + xaxis_rangeslider_visible=False + ) + self.base_figure.update_yaxes(title_text="Price", row=1, col=1) + if self.show_volume: + self.base_figure.update_yaxes(title_text="Volume", row=2, col=1) + self.base_figure.update_xaxes(title_text="Time", row=self.rows, col=1) + + +def get_bar_plot_volume_of_trades(strategy_data: StrategyData): + grouped_df = strategy_data.trade_fill.groupby("trade_type").agg({"amount": "sum", "order_id": "count"}) + # Create figure with secondary y-axis + fig = go.Figure() + fig.add_trace(go.Bar( + y=['Total Amount'], + x=grouped_df.loc["BUY", ["amount"]], + name='Buy Amount', + orientation='h', + + )) + fig.add_trace(go.Bar( + y=['Total Amount'], + x=grouped_df.loc["SELL", ["amount"]], + name='Sell Amount', + orientation='h', + )) + fig.update_layout(template="plotly_white", title="Volume analysis", height=300, + xaxis_title="Amount in Base Asset") + return fig + + +def get_bar_plot_quantity_of_trades(strategy_data: StrategyData): + grouped_df = strategy_data.trade_fill.groupby("trade_type").agg({"amount": "sum", "order_id": "count"}) + + fig = go.Figure() + fig.add_trace(go.Bar( + y=['Quantity of Orders'], + x=grouped_df.loc["BUY", ["order_id"]], + name='Quantity of Buys', + orientation='h', + + )) + fig.add_trace(go.Bar( + y=['Quantity of Orders'], + x=grouped_df.loc["SELL", ["order_id"]], + name='Quantity of Sells', + orientation='h', + )) + fig.update_layout(template="plotly_white", title="Excution Analysis", height=300, + xaxis_title="Quantity of orders") + return fig \ No newline at end of file