python/dantimofte/bfxtelegram/bfxtelegram/tgraph.py

tgraph.py
#!/usr/bin/env python3
"""
docstring
"""

from datetime import datetime, timedelta
from math import pi
import pandas as pd

# bokeh libraries
from bokeh.plotting import figure
from bokeh.io import export_png
from bokeh.layouts import layout


COLOR_THEME = {
    "normal":
    {"up": "#98FB98", "down": "#FF0000", "sell_order": "#FF0000", "buy_order": "#98FB98"},
    "colorblind":
    {"up": "#00ee00", "down": "#ff00ff", "sell_order": "#ff00ff", "buy_order": "#00ee00"},
    "monochrome":
    {"up": "white", "down": "black", "sell_order": "black", "buy_order": "black"}
}


class Tgraph:
    def __init__(self, candles_data, active_orders, orders_data, symbol, **kwargs):
        self.colors = self.set_colors(**kwargs)
        self.symbol = symbol
        self.candles_data = candles_data
        self.active_orders = active_orders
        self.orders_data = orders_data

        candles_df = self.build_dataframe()

        self.candle_width = 30 * 60 * 1000  # half an hour in ms

        self.x_min = candles_df['date'].min() - timedelta(hours=1)
        self.x_max = candles_df['date'].max() + timedelta(hours=1)

        cdl_graph = self.build_candles_graph(candles_df)
        ao_graph = self.build_active_orders_graph()
        vol_graph = self.build_volume_graph(candles_df)
        rsi_graph = self.build_rsi_graph(candles_df)

        self.graphs_layout = layout(
            children=[
                [cdl_graph, ao_graph],
                [vol_graph],
                [rsi_graph]
            ]
        )

    def build_dataframe(self):
        candles_df = pd.DataFrame(
            self.candles_data,
            columns=['date', 'open', 'close', 'high', 'low', 'volume']
        )

        def get_date(unixtime):
            time_stamp = unixtime / 1000
            formated_date = datetime.utcfromtimestamp(time_stamp).strftime('%Y-%m-%d %H:%M:%S')
            return formated_date

        candles_df['date'] = candles_df['date'].apply(get_date)
        candles_df['volume'] = candles_df['volume'].astype(int)
        candles_df["date"] = pd.to_datetime(candles_df["date"])

        # Window length for moving average
        window_length = 14
        candles_df['seq'] = candles_df['date']
        close = candles_df['close'][::-1]
        # Get the difference in price from previous step
        delta = close.diff()
        # Make the positive gains (up) and negative gains (down) Series
        up_gains, down_gains = delta.copy(), delta.copy()
        up_gains[up_gains  <  0] = 0
        down_gains[down_gains > 0] = 0
        # Calculate the EWMA
        roll_up1 = up_gains.ewm(
            com=window_length,
            min_periods=0,
            adjust=True,
            ignore_na=False
        ).mean()

        roll_down1 = down_gains.abs().ewm(
            com=window_length,
            min_periods=0,
            adjust=True,
            ignore_na=False
        ).mean()

        # Calculate the RSI based on EWMA
        rs1 = roll_up1 / roll_down1
        rsi1 = 100.0 - (100.0 / (1.0 + rs1))
        candles_df['rsi_ewma'] = rsi1
        candles_df = candles_df[::-1][16:]

        return candles_df

    def build_candles_graph(self, candles_df):
        x_text = candles_df['date'].min()

        candles_graph = figure(
            x_axis_type="datetime",
            toolbar_location=None,
            x_range=(self.x_min, self.x_max),
            plot_width=1000,
            title=self.symbol)
        candles_graph.title.text_font_size = "25pt"
        candles_graph.yaxis.major_label_text_font_size = "15pt"
        candles_graph.yaxis[0].ticker.desired_num_ticks = 20

        candles_graph.xaxis.major_label_text_font_size = "12pt"
        candles_graph.xaxis[0].ticker.desired_num_ticks = 30
        candles_graph.xaxis.major_label_orientation = pi / 2
        candles_graph.grid.grid_line_alpha = 0.3

        candles_graph.segment(
            candles_df.date,
            candles_df.high,
            candles_df.date,
            candles_df.low,
            color="black"
        )

        inc = candles_df.close > candles_df.open
        candles_graph.vbar(
            candles_df.date[inc],
            self.candle_width,
            candles_df.open[inc],
            candles_df.close[inc],
            fill_color=self.colors['up'],
            line_color="black"
        )
        dec = candles_df.open > candles_df.close
        candles_graph.vbar(
            candles_df.date[dec],
            self.candle_width,
            candles_df.open[dec],
            candles_df.close[dec],
            fill_color=self.colors['down'],
            line_color="black"
        )

        sells = []
        buys = []
        max_price = float(candles_df['high'].max())
        min_price = float(candles_df['low'].min())
        for order in self.active_orders:
            price = float(order['price'])
            if price > max_price or price  <  min_price:
                continue
            line = [price] * candles_df.shape[0]
            if order['side'] == 'sell':
                sign = "-"
                sells.append(line)
                candles_graph.rect(
                    x=x_text + timedelta(hours=3),
                    y=price + 0.002,
                    width=timedelta(hours=7),
                    height=0.005,
                    fill_color="white"
                )
            else:
                buys.append(line)
                sign = "+"
                candles_graph.rect(
                    x=x_text + timedelta(hours=3),
                    y=price + 0.002,
                    width=timedelta(hours=7),
                    height=0.005,
                    fill_color="white"
                )
            candles_graph.text(
                x=x_text,
                y=price,
                text=[f"{sign}{order['remaining_amount']}"],
                text_font_size='8pt',
                text_font_style='bold'
            )

        # add current price
        current_price = float(candles_df['close'][0])
        new_line = [current_price] * candles_df.shape[0]
        if candles_df['open'][0] > candles_df['close'][0]:
            sells.append(new_line)
            candles_graph.rect(
                x=x_text + timedelta(hours=3),
                y=current_price + 0.002,
                width=timedelta(hours=7),
                height=0.005,
                fill_color="white"
            )
        else:
            buys.append(new_line)
            candles_graph.rect(
                x=x_text + timedelta(hours=3),
                y=current_price + 0.002,
                width=timedelta(hours=7),
                height=0.005,
                fill_color="white"
            )

        candles_graph.text(
            x=x_text,
            y=current_price,
            text=[f"{current_price}"],
            text_font_size='8pt',
            text_font_style='bold'
        )

        # add sell lines
        candles_graph.multi_line(
            xs=[candles_df.seq] * len(sells),
            ys=sells,
            line_color=self.colors['sell_order'],
            line_dash="dashed",
            line_width=1
        )

        # add buy lines
        candles_graph.multi_line(
            xs=[candles_df.seq] * len(buys),
            ys=buys,
            line_color=self.colors['buy_order'],
            line_width=1
        )
        return candles_graph

    def build_rsi_graph(self, candles_df):
        rsi_graph = figure(
            plot_width=1000,
            plot_height=100,
            x_range=(self.x_min, self.x_max),
            toolbar_location=None,
            y_range=(0, 100),
            title="Relative Strength Index"
        )
        rsi_graph.xaxis.visible = False
        rsi_graph.multi_line(
            xs=[candles_df.seq] * 4,
            ys=[
                candles_df.rsi_ewma,
                [20] * candles_df.shape[0],
                [50] * candles_df.shape[0],
                [80] * candles_df.shape[0]
            ],
            line_color=['red', 'black', 'gray', 'black'],
            line_width=1
        )
        rsi_graph.yaxis.major_label_text_font_size = "11pt"

        return rsi_graph

    def build_volume_graph(self, candles_df):
        # Historic Volume GRAPH
        volume_max = int(candles_df['volume'].max())
        volume_graph = figure(
            plot_width=1000,
            plot_height=100,
            x_range=(self.x_min, self.x_max),
            toolbar_location=None,
            y_range=(0, volume_max),
            title="Volume"
        )
        volume_graph.xaxis.visible = False
        volume_graph.left[0].formatter.use_scientific = False

        volume_graph.vbar(
            candles_df.date,
            self.candle_width,
            candles_df.volume * 0,
            candles_df.volume,
            fill_color="blue",
            line_color="black"
        )
        return volume_graph

    def build_active_orders_graph(self):
        # Volume in active orders GRAPH
        orderbook_df = pd.DataFrame.from_dict(self.orders_data, orient='columns')

        orders_vol_graph = figure(
            plot_width=200,
            toolbar_location=None,
            y_range=orderbook_df.price,
            title="Orderbook"
        )
        orders_vol_graph.below[0].formatter.use_scientific = False
        orders_vol_graph.yaxis.visible = False
        orders_vol_graph.xaxis.major_label_text_font_size = "8pt"
        orders_vol_graph.xaxis[0].ticker.desired_num_ticks = 5
        orders_vol_graph.xaxis.major_label_orientation = pi / 2

        orders_vol_graph.hbar(
            y=orderbook_df.price,
            left=orderbook_df.amount * 0,
            right=orderbook_df.amount,
            height=0.1
        )

        return orders_vol_graph

    def set_colors(self, **kwargs):
        colors_dict = {}
        colors_dict['up'] = COLOR_THEME['normal']['up']
        colors_dict['down'] = COLOR_THEME['normal']['down']
        colors_dict['sell_order'] = COLOR_THEME['normal']['sell_order']
        colors_dict['buy_order'] = COLOR_THEME['normal']['buy_order']
        for key, value in kwargs.items():
            if key == 'graphtheme':
                colors_dict['up'] = COLOR_THEME[value]['up']
                colors_dict['down'] = COLOR_THEME[value]['down']
                colors_dict['sell_order'] = COLOR_THEME[value]['sell_order']
                colors_dict['buy_order'] = COLOR_THEME[value]['buy_order']
        return colors_dict

    def save_picture(self):
        # export the graph
        export_png(self.graphs_layout, filename="graph.png")