# <include-carry_trade/utils.py>
# <imports>
import numpy as np
import pandas as pd
import plotly.io as pio
from carry_trade import utils
pd.options.plotting.backend = "plotly"
pio.templates.default = "seaborn"
Here we analyze three different carry trade strategies involving cross-currency fixed-float and basis swaps. In each we borrow Japanese Yen at three-month Libor + 50bps and use 80% leverage to purchase 5-year government bonds in one of the following markets:
Our investment period spans from 2015-01-01 to 2021-04-22. We mark to market back to USD on a weekly basis.
start_date = "2014-12-01"
tickers = ["THA", "ROU", "JPN", "IDN"]
libors = ["JPY3MTD156N"]
currencies = ["THB", "RON", "JPY", "IDR"]
dfs_yc = utils.load_yc(tickers)
dfs_fx = utils.load_fx(currencies)
dfs_libor = utils.load_libor(libors)
strategies = [
{"yc_L": "THA", "fx_B": "JPY", "fx_L": "THB", "libor": "JPY3MTD156N", "leverage": 0.8},
{"yc_L": "ROU", "fx_B": "JPY", "fx_L": "RON", "libor": "JPY3MTD156N", "leverage": 0.8},
{"yc_L": "IDN", "fx_B": "JPY", "fx_L": "IDR", "libor": "JPY3MTD156N", "leverage": 0.8},
]
date_range = pd.date_range("2015-01-01", "2021-04-26", freq="7D")
df_ret_0, df_profit_0 = utils.run_strategy(
**strategies[0],
date_range=date_range,
dfs_yc=dfs_yc,
dfs_fx=dfs_fx,
dfs_libor=dfs_libor
)
Distributions of returns
Drivers of profit and loss
2015Q1
2015Q2 - 2016Q3
2016Q3 - 2020Q1
2020Q1
utils.make_returns_chart(df_ret_0)
fig = utils.make_components_chart(
**strategies[0],
date_range=date_range,
dfs_yc=dfs_yc,
dfs_fx=dfs_fx,
dfs_libor=dfs_libor
)
fig.show()
For illustrative purposes, below are the returns for this strategy including only the favorable period with no big volaltility swings from 2016Q3 to 2020Q1.
df_ret_0a, df_profit_0a = utils.run_strategy(
**strategies[0],
date_range=pd.date_range("2016-10-01", "2020-02-28", freq="7D"),
dfs_yc=dfs_yc,
dfs_fx=dfs_fx,
dfs_libor=dfs_libor
)
utils.make_returns_chart(df_ret_0a)
df_ret_1, df_profit_1 = utils.run_strategy(
**strategies[1],
date_range=date_range,
dfs_yc=dfs_yc,
dfs_fx=dfs_fx,
dfs_libor=dfs_libor
)
Distributions of returns
Drivers of profit and loss
utils.make_returns_chart(df_ret_1)
fig = utils.make_components_chart(
**strategies[1],
date_range=date_range,
dfs_yc=dfs_yc,
dfs_fx=dfs_fx,
dfs_libor=dfs_libor
)
fig.show()
df_ret_2, df_profit_2 = utils.run_strategy(
**strategies[2],
date_range=date_range,
dfs_yc=dfs_yc,
dfs_fx=dfs_fx,
dfs_libor=dfs_libor
)
Distributions of returns
Drivers of profit and loss
utils.make_returns_chart(df_ret_2)
fig = utils.make_components_chart(
**strategies[2],
date_range=date_range,
dfs_yc=dfs_yc,
dfs_fx=dfs_fx,
dfs_libor=dfs_libor
)
fig.show()
df_ret_0b, df_profit_0b = utils.run_strategy(
**strategies[0],
date_range=pd.date_range("2015-03-01", "2021-04-26", freq="3D"),
dfs_yc=dfs_yc,
dfs_fx=dfs_fx,
dfs_libor=dfs_libor
)
utils.make_returns_chart(df_ret_0b)
fig = utils.make_components_chart(
**strategies[0],
date_range=pd.date_range("2015-03-01", "2021-04-26", freq="3D"),
dfs_yc=dfs_yc,
dfs_fx=dfs_fx,
dfs_libor=dfs_libor
)
fig.show()
This is a bit of a dive into the weeds to make sure the mechanics of the returns calculations are being peformed correctly and also to understand the relative importance of the yield movements versus the fx movements. This looks at the week ending 2020-04-02, which was the single biggest down week in the Thai Bhat strategy, down 21.1%
row = df_profit_0.loc["2020-04-02"].to_dict()
row
{'fx_B0': 110.6239, 'fx_B1': 107.485056, 'fx_L0': 32.815, 'fx_L1': 33.09, 'zcb_L0': 0.00990235531649793, 'zcb_L0t': 5.0, 'zcb_L1': 0.011222842161769251, 'zcb_L1t': 4.980821917808219, 'V_L0': 328150000.00000006, 'N_L0': 344806226.70035625, 'V_L1': 326060719.2145738, 'V_B0': 1106239000.0000002, 'V_B1': 1059131298.4037092, 'K_H0': 2000000.0, 'K_H1': 1619426.5519234003, 'r_H1': -0.211075074275663}
((row["V_B0"] * row["fx_L0"] / row["fx_B0"]) / np.exp(-row["zcb_L0"] * row["zcb_L0t"]) * np.exp(-row["zcb_L1"] * row["zcb_L1t"]) / row["fx_L1"] * row["fx_B1"] - row["V_B0"] * 0.8) / row["fx_B1"]
1620133.1132367735