ABOUT THE MODEL

The fx_hedge_var_impact.ipynb supports the calculation of parametric Value-at-Risk of an FX portfolio and allows the user to evaluate the impact of hedging different currency positions on the portfolio's risk profile. This script is designed to be imported as a module into other notebooks using the ipynb python library and used by calling the main calculation function:
 fx_hedge_var_impact.get_data({ric : [cash_amount, hedge_ratio]})
The result is presented as a plotly chart that compares the FX risk profile prior and after hedging. Note that the portfolio should be declared as a dictionary following the above structure.
Let's have a look at how the model works in more detail.

 

DEPENDENCIES AND CONSTANTS

Since the model will be consuming Refinitiv data, performing operations with matrices and plotting charts, we will need to import the following libraries:
import eikon as ek
import pandas as pd
import numpy as np
import cufflinks as cf
import plotly.graph_objs as go
import plotly.subplots as subplots

from datetime import datetime
from plotly.offline import init_notebook_mode, iplot, plot

cf.go_offline()
init_notebook_mode(connected=True)

APP_KEY = 'YOUR_APP_KEY'
ek.set_app_key(APP_KEY)

Once this is done, we will declare 2 constants:

direct_qm = ['AUD=', 'BTC=', 'BWP=', 'EUR=', 'FJD=', 'FKP=', 'GBP=', 'GIP=', 'IEP=', 'MTL=', 'NZD=', 'PGK=', 'SBD=', 'SHP=', 'TOP=', 'USD=', 'WST=', 'XAG=', 'XAU=','XPD=', 'XPT=']
z_score_const = 1.64485
  • direct_qm contains a list of instruments that have a direct quotation method.
  • z_score_const is the z-score that corresponds to a 95% confidence interval. The user can enhance this to a list of commonly used values in case there is a need to run calculations at other confidence intervals.

 

FUNCTION DEFINITIONS

In addition to the main get_data() function, this model contains a few more definitions, that will be used during the VaR calculation:

def convert_to_usd(ric, rate, amt):
    if ric in direct_qm:
        return amt * rate
    else:
        if rate != 0:
            return amt / rate
        else:
            return 0

def get_annual_stdev(risk_array, ann_stdev):
    arr_A = np.array(risk_array)
    arr_B = np.array(ann_stdev)
    annual_stdev = list(np.multiply(arr_A, arr_B))
    return annual_stdev

def get_port_vol(corr_mtx, stdev):
    _v = pd.DataFrame(corr_mtx.dot(stdev)).transpose()
    port_vol = np.dot(np.array(_v), np.array(pd.DataFrame(stdev)))[0][0] ** 0.5
    return port_vol

 

A few comments on the above functions:

  • convert_to_usd() converts the initial portfolio positions from the original currencies to USD. This is where the script will run a check against the global list of instruments with a direct quotation method.
  • get_annual_std() is used to perform element-wise multiplication of weight of each position in the portfolio.
  • get_port_vol() performs a matrix multiplication to derive the portfolio's variance first by multiplying the row vector of weighted volatilities for each currency, the correlation matrix and the transposed vector of currency volatilities. Then the volatility is returned by taking a sqaure root of the variance.

 

THE CALCULATION

Now that all declarations have been made, let's have a look at the main function using a model FX portfolio:
EXAMPLE
In this example we will be building a risk profile on 
portfolio of 5 currencies: SGD, EUR, GBP, JPY and CHF
with a total value of 40+ mln USD.
 
The holding structure is displayed on the diagram to 
the right. We will be hedging 3 positions as follows:
- EUR: 50%
- GBP: 30%
- CHF: 20%
portfolio = {
    'SGD=':[10000000, 0], 
    'EUR=':[10000000, 0.5], 
    'GBP=':[10000000, 0.3],
    'JPY=':[100000000, 0],
    'CHF=':[10000000, 0.2]
}

Note that the cash values are given in the held currencies.

After intaking the portfolio dictionary, the get_data() function will parse the given data and retrive historical rates for the given RICs using the Data API:

def get_data(portfolio):    
    instruments = list(portfolio.keys())
    df = ek.get_timeseries(instruments, 'CLOSE', interval='daily', count=252)
    df = df.fillna(method='ffill')
    positions = list(map(lambda p: portfolio[p][0], instruments))
    hedge_ratios = list(map(lambda p: portfolio[p][1], instruments))
    spot_fx = df.tail(1).values.tolist()[0]
    . . .

Additionally the cash positions, hedge ratios and spot FX rates are stored in separate variables.

The next step is to calculate the weights of the held currencies in the portfolio, which is achieved by converting the position into USD first:

flow_usd = list(map(lambda r, f, a: convert_to_usd(r, f, a), instruments, spot_fx, positions))
risk_amt_before_hedge = round(sum(flow_usd), 2)
risk_pct_before_hedge = list(np.array(flow_usd) / risk_amt_before_hedge)
hedge_usd = list(np.multiply(hedge_ratios, flow_usd))
remaining_flow_usd = list(np.subtract(np.array(flow_usd), np.array(hedge_usd)))
risk_amt_after_hedge = round(sum(remaining_flow_usd), 2)
risk_pct_after_hedge = list(np.array(remaining_flow_usd) / risk_amt_after_hedge)

 

The calculated weights of FX positions prior to and after hedging are stored in the risk_pct_before_hedge and risk_pct_after_hedge respectively. 

Now we move on to calculating the log returns from the retrieved timeseries with numpy. The result is stored in a new pandas dataframe df_log

#calculate log returns
df_log = pd.DataFrame()
df_log = np.log(df) - np.log(df.shift(1))
df_log = df_log.dropna(axis=0)

 

We can quickly check the data in df_log using cufflinks:

In [2]:

df_log.iplot(title='Portfolio log returns')
Out [2]:

Below is a chart with the distribution of these log returns:

We will be using this data to calculate the correlation matrix for the VaR. The next snippet shows the calculation of the means, standard deviations and excess returns:

df_stats = df_log.describe()
daily_std = list(map(lambda c: df_stats[c]['std'], list(df_stats.columns)))
daily_avg = list(map(lambda c: df_stats[c]['mean'], list(df_stats.columns)))
annualized_std = list(map(lambda x: x * (252 ** 0.5), daily_std))

#calculate excess returns
df_x_ret = df_log
for n, c in enumerate(df_x_ret.columns):
    df_x_ret[c] -= daily_avg[n]

ann_std_hedged = get_annual_stdev(risk_pct_after_hedge, annualized_std)
ann_std_unhedged = get_annual_stdev(risk_pct_before_hedge, annualized_std)

 

We can now at this stage we could calculate the variance-covarianc matrix on the obtained excess returns, however we will not isolate it into a separate operation in the script, and go straight for the correlation matrix using pandas. Let's visualize the result using a heatmap:

In [3]:

corr_mtx = df_x_ret.corr()
corr_mtx.iplot('heatmap', colorscale='YlGnBu', title='FX correlation matrix')

Out [3]:

14

Finally we approach the calculation of Value-at-Risk, which follows the equation:

VaR = V × zα  × σ

where V is the cash value of the portfolio, σ is the portfolio's volatility and zα the z-score corresponding to the confidence inerval α.

vol_after_hedge = get_port_vol(corr_mtx, ann_std_hedged)
vol_before_hedge = get_port_vol(corr_mtx, ann_std_unhedged)
var_before_hedge = (vol_before_hedge * np.abs(risk_amt_before_hedge) * z_score_const).round(2)
var_after_hedge = (vol_after_hedge * np.abs(risk_amt_after_hedge) * z_score_const).round(2)

 

We can now look at the result by displaying the VaR before and after hedging in a pandas dataframe.

In [4]:

df_pct_risk = pd.DataFrame()
df_pct_risk['% Risk after hedge'] = risk_pct_after_hedge
df_pct_risk['% Risk before hedge'] = risk_pct_before_hedge
df_pct_risk.index = instruments

value_at_risk = pd.DataFrame()
value_at_risk['Risk amount, USD'] = [risk_amt_before_hedge, risk_amt_after_hedge]
value_at_risk['Volatility'] = [vol_before_hedge, vol_after_hedge]
value_at_risk['VaR, USD'] = [var_before_hedge, var_after_hedge]
value_at_risk.index = ['Before hedge', 'After hedge']

value_at_risk

Out [4]:

  Risk amount, USD Volatility VaR, USD
Before hedge 42619647.56 0.023466 1645061.12
After hedge 31096516.17 0.022117 1131265.59
The final snippet of the get_data() function wraps the analytics into a plotly figure and plots the portfolio's risk profile as follows:
#plot VaR & risk weights
trace1 = go.Heatmap(x = df_pct_risk.index, y = df_pct_risk.columns, z = df_pct_risk.T.values * 100, colorscale='YlGnBu')
trace2 = go.Bar(x = value_at_risk.index, y = value_at_risk['VaR, USD'].values, marker=dict(color='darkblue'))
fig = subplots.make_subplots(shared_yaxes=False, shared_xaxes=False, rows=1, cols=2, 
                        subplot_titles = ('VaR chg, USD','Risk distribution chg, %'), print_grid = False)
fig.append_trace(trace1, row= 1, col=2)
fig.append_trace(trace2, row= 1, col=1)
fig.layout.title = 'FX hedge impact analysis'
fig.layout.xaxis=dict(domain = [0, 0.25])
fig.layout.xaxis2=dict(domain = [0.45, 1])
iplot(fig)