8.1. Hàm SeasonalityPrice

Hàm này dùng để vẽ tương quan thay đổi giá của 1 hay nhiều mã cổ phiếu trong một khoảng thời gian

import pandas as pd
import numpy as np
from plotly import graph_objects as go
from plotly.subplots import make_subplots
import pandas as pd
from FiinQuantX import FiinSession

username = 'REPLACE_WITH_YOUR_USER_NAME'
password = 'REPLACE_WITH_YOUR_PASS_WORD'

client = FiinSession(
    username=username,
    password=password
).login()

class SeasonalityPrice:
    def __init__(self, data, tickers):
        self.data = data
        self.data['timestamp'] = pd.to_datetime(self.data['timestamp'])
        self.tickers = []
        if isinstance(tickers, str):
            self.tickers = [tickers]
        elif isinstance(tickers, list):
            self.tickers = tickers
        else:
            self.tickers = [tickers]

    def monthly_seasonality(self):
        if len(self.tickers) > 1:
            raise ValueError("Only one ticker is supported for monthly seasonality")
        
        filtered_data = self.data.copy()
        filtered_data.set_index('timestamp', inplace=True)

        monthly_returns = filtered_data['close'].resample('M').last().pct_change() * 100
        monthly_seasonality = pd.DataFrame()
        monthly_seasonality['Month'] = monthly_returns.index.month
        monthly_seasonality['Year'] = monthly_returns.index.year
        monthly_seasonality['Returns'] = monthly_returns.values

        return monthly_seasonality
    
    
    def plot_seasonality(self):  
        if len(self.tickers) > 1:
            print("Tickers: ", self.tickers)

            raise ValueError("Only one ticker is supported for monthly seasonality")
        
        monthly_seasonality = self.monthly_seasonality()
        pivot_table = monthly_seasonality.pivot(index='Year', columns='Month', values='Returns').sort_index(ascending=True)
        monthly_averages = pivot_table.mean(axis=0).values.reshape(1, -1)  
        monthly_stdev = pivot_table.std(axis=0).values.reshape(1, -1)
        monthly_avg_plus_stdev = monthly_averages + monthly_stdev
        monthly_avg_minus_stdev = monthly_averages - monthly_stdev
        monthly_Sharpe_ratio = monthly_averages / monthly_stdev
        annotations = []
        for i, row in enumerate(pivot_table.values):
            for j, value in enumerate(row):
                annotations.append(
                    dict(
                        x=['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'][j],
                        y=pivot_table.index[i],
                        text=f'{value:.2f}' if not np.isnan(value) else '',
                        showarrow=False,
                        font=dict(color='black' if abs(value) < 10 else 'white')
                    )
                )
        fig = go.Figure()

        fig.add_trace(go.Heatmap(
            z=pivot_table.values,
            x=['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'],
            y=pivot_table.index,  
            colorscale=[
                [0, 'darkred'],      
                [0.49, '#ff6666'],   
                [0.5, 'white'],      
                [0.51, '#99ff99'],   
                [1, 'darkgreen']     
            ],
            colorbar_title="Returns (%)",
            showscale=False,
            zmin=-20,   
            zmax=20,
        ))

        # Add the averages heatmap
        fig.add_trace(go.Heatmap(
            z=monthly_averages,
            x= ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'],
            y=['Avgs: '],
            colorscale=[
                [0, 'darkred'],      
                [0.49, '#ff6666'],   
                [0.5, 'white'],      
                [0.51, '#99ff99'],   
                [1, 'darkgreen']     
            ],
            showscale=False,
            showlegend=False,
            text=monthly_averages,
            texttemplate='%{z:.2f}%',
            yaxis='y2',  
            xaxis='x2', 
        ))

        fig.add_trace(go.Heatmap(
            z=monthly_stdev,
            x= ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'],
            y=['Stdev: '],
            colorscale=[
                [0, 'darkred'],      
                [0.49, '#ff6666'],   
                [0.5, 'white'],      
                [0.51, '#99ff99'],   
                [1, 'darkgreen']     
            ],
            text=monthly_stdev,
            texttemplate='%{z:.2f}%',
            showscale=False,
            showlegend=False,
            yaxis= 'y3',
            xaxis= 'x3',
        ))
        fig.add_trace(go.Heatmap(
            z=monthly_avg_plus_stdev,
            x=['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'],
            y=['+ 1 stdev: '],
            colorscale=[
                [0, 'darkred'],      
                [0.49, '#ff6666'],   
                [0.5, 'white'],      
                [0.51, '#99ff99'],   
                [1, 'darkgreen']     
            ],
            showscale=False,
            showlegend=False,
            text=monthly_avg_plus_stdev,
            texttemplate='%{z:.2f}%',
            yaxis='y4',
            xaxis='x4',
        ))

        # Add the avg - 1stdev heatmap
        fig.add_trace(go.Heatmap(
            z=monthly_avg_minus_stdev,
            x=['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'],
            y=[' -1 stdev: '],
            colorscale=[
                [0, 'darkred'],      
                [0.49, '#ff6666'],   
                [0.5, 'white'],      
                [0.51, '#99ff99'],   
                [1, 'darkgreen']     
            ],
            showscale=False,
            showlegend=False,
            text=monthly_avg_minus_stdev,
            texttemplate='%{z:.2f}%',
            yaxis='y5',
            xaxis='x5',
        ))

        fig.update_layout(
            title=f'Monthly Percentage Price Change for {self.tickers} (2018-2024)',
            xaxis=dict(domain=[0, 1], showticklabels=False),
            yaxis=dict(domain=[0.5, 1.0], title='Year', autorange='reversed'),  
            xaxis2=dict(domain=[0, 1], anchor='y2', matches='x', showticklabels=False),
            yaxis2=dict(domain=[0.35,0.45], autorange='reversed'), 
            xaxis3=dict(domain=[0, 1], anchor='y3', matches='x',showticklabels=False),
            yaxis3=dict(domain=[0.24,0.34], autorange='reversed'), 
            yaxis4=dict(domain=[0.13,0.23], autorange='reversed'), 
            xaxis4=dict(domain=[0, 1], anchor='y4', matches='x', showticklabels=False),
            yaxis5=dict(domain=[0.02,0.12], autorange='reversed'), 
            xaxis5=dict(domain=[0, 1], anchor='y5', matches='x', title='Month'),
            annotations=annotations,
        )

        fig.show()

        fig_sharpe = go.Figure()

        sharpe_values = monthly_Sharpe_ratio.flatten()
        colors = ['#ff9999' if x < 0 else '#66b3ff' if x < 1 else '#99ff99' for x in sharpe_values]

        fig_sharpe.add_trace(go.Bar(
            x=['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'],
            y=sharpe_values,
            marker_color=colors,
            name='Sharpe Ratio'
        ))

        fig_sharpe.update_layout(
            title=f'Monthly Sharpe Ratio for {self.tickers} (2018-2024)',
            xaxis=dict(title='Month'),
            yaxis=dict(title='Sharpe Ratio'),
            plot_bgcolor='white',
            paper_bgcolor='white',
            showlegend=False,
            bargap=0.2
        )

        fig_sharpe.show()


    def plot_average_sharpe(self):
        monthly_sharpe_ratios = {}
        average_sharpe_ratios = {}

        for ticker in self.tickers:
            # Filter data for the current ticker
            ticker_data = self.data[self.data['ticker'] == ticker]
            ticker_data['timestamp'] = pd.to_datetime(ticker_data['timestamp'])
            mask = (ticker_data['timestamp'] >= '2018-01-01') & (ticker_data['timestamp'] <= '2024-11-30')
            filtered_data = ticker_data[mask].copy()

            filtered_data.set_index('timestamp', inplace=True)
            monthly_returns = filtered_data['close'].resample('ME').last().pct_change() * 100

            # Calculate monthly averages and standard deviations
            monthly_averages = monthly_returns.groupby(monthly_returns.index.month).mean()
            monthly_stdev = monthly_returns.groupby(monthly_returns.index.month).std()

            # Calculate Sharpe ratio
            sharpe_ratios = (monthly_averages / monthly_stdev).values
            monthly_sharpe_ratios[ticker] = sharpe_ratios
            average_sharpe_ratios[ticker] = np.nanmean(sharpe_ratios) 

        # Find the ticker with the highest average Sharpe ratio
        highest_avg_sharpe_ticker = max(average_sharpe_ratios, key=average_sharpe_ratios.get)
        highest_avg_sharpe_value = average_sharpe_ratios[highest_avg_sharpe_ticker]

        # Create subplots
        fig = make_subplots(rows=1, cols=2, shared_yaxes=False, horizontal_spacing=0.1,
                            subplot_titles=("Monthly Sharpe Ratios", "Average Sharpe Ratios"))

        months = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec']
        
        # Plot monthly Sharpe ratios
        for ticker, sharpe_values in monthly_sharpe_ratios.items():
            colors = ['#00FF00' if ticker == highest_avg_sharpe_ticker else '#ff9999' if value < 0 else '#66b3ff' if value < 1 else '#99ff99' for value in sharpe_values]
                    
            fig.add_trace(go.Bar(
                x=months,
                y=sharpe_values,
                marker_color=colors,
                name=f'Sharpe Ratio for {ticker}',
                width=0.15
            ), row=1, col=1)

        # Plot average Sharpe ratios
        avg_sharpe_values = list(average_sharpe_ratios.values())
        avg_colors = ['#00FF00' if ticker == highest_avg_sharpe_ticker else '#66b3ff' for ticker in tickers]
        
        fig.add_trace(go.Bar(
            x=tickers,
            y=avg_sharpe_values,
            marker_color=avg_colors,
            name='Average Sharpe Ratio',
            width=0.4
        ), row=1, col=2)

        # Add annotation for the highest average Sharpe ratio
        fig.add_annotation(
            x=highest_avg_sharpe_ticker, y=highest_avg_sharpe_value,
            text=f"Highest Avg Sharpe: {highest_avg_sharpe_ticker} ({highest_avg_sharpe_value:.2f})",
            showarrow=True,
            arrowhead=1,
            yshift=10,
            font=dict(size=12, color="black"),
            xref="x2", yref="y2"
        )

        fig.update_layout(
            title='Sharpe Ratios for Multiple Tickers (2018-2024)',
            xaxis=dict(title='Month'),
            yaxis=dict(title='Sharpe Ratio'),
            xaxis2=dict(title='Ticker'),
            yaxis2=dict(title='Average Sharpe Ratio', range=[0, max(avg_sharpe_values) * 1.2]),  # Scale y-axis
            plot_bgcolor='white',
            paper_bgcolor='white',
            showlegend=False,
            bargap=0.2
        )

        fig.show()


tickers = ['VCB', 'TPB', 'MBB','VIB','TCB', 'VPB', 'ACB', 'BID','CTG','EIB']

data = client.Fetch_Trading_Data(
    tickers=tickers,
    fields=['close'],
    adjusted=True,
    realtime=False,
    by='1d', 
    from_date='2018-01-01').get_data()
new_SeasonalityPrice = SeasonalityPrice(data, 'VCB')
new_SeasonalityPrice.plot_seasonality()

#Uncomment this to plot average sharpe ratio for multiple tickers
tickers = ['VCB', 'TPB', 'MBB','VIB','TCB', 'VPB', 'ACB', 'BID','CTG','EIB']
# new_SeasonalityPrice = SeasonalityPrice(data, tickers)
# new_SeasonalityPrice.plot_average_sharpe()

Bảng thay đổi giá theo tháng của VCB
Sharpe Ratio theo tháng

Last updated