diff --git a/README.md b/README.md
index aa99cb5..a545af2 100644
--- a/README.md
+++ b/README.md
@@ -23,6 +23,14 @@
:newspaper: **Последние обновления**:
+Апрель 2022:
+
+- Для формирования низкорискованного валютного портфеля:
+ - добавил скрипт [анализа волатильности валют](src/fx_currencies_analysis.md)
+ - добавил скрипт [моделирования цен валют с использованием метода Монте-Карло](src/fx_currency_portfolio__assets_selection.ipynb)
+
+Март 2022:
+
- Обновление списка [стейблкоинов](lists.md#stablecoins)
- Рефакторинг списка [VPN сервисов](lists.md#vpn)
- Обновлена структура `README.md`
diff --git a/faq.md b/faq.md
index a09b1a6..d3045cc 100644
--- a/faq.md
+++ b/faq.md
@@ -14,8 +14,15 @@
### Как выбрать соотношение рублей, иностранной валюты для наличных средств?
-Оценить вероятность, что ЦБ введет фиксированный курс с одновременным запретом покупки/продажи валюты на открытом рынке.
-Чем выше эта вероятность, тем бОльшая доля наличных средств в рублях должна быть в корзине; максимальную долю рублей имеет смысл ограничиться 80%.
+Оценить вероятность, что ЦБ введет фиксированный курс с одновременным запретом покупки/продажи валюты на открытом рынке.
+
+Чем выше эта вероятность, тем бОльшая доля наличных средств в рублях должна быть в корзине; максимальную долю рублей имеет смысл ограничиться 80%.
Чем ниже эта вероятность, тем бОльшая доля наличных средств в иностранной валюте должна быть. Минимальная долю рубля имеет смысл ограничить 30%.
+
+### Как сформировать низкорискованный валютный портфель?
+
+1. [Проанализируйте волатильности валют](src/fx_currencies_analysis.md) на основе исторических данных и выберете те, риски по которым ниже рублевых.
+2. [Смоделируйте цены валют](src/fx_currency_portfolio__assets_selection.ipynb), выбранных на шаге 1, с использованием метода Монте-Карло.
+3. Основываясь на результатах моделирования, выберите наиболее подходящии Вам по соотношению доходность/риски валютные пары.
diff --git a/src/cryptocurrency_portfolio__assets_selection.py b/src/cryptocurrency_portfolio__assets_selection.py
new file mode 100644
index 0000000..fdf26f9
--- /dev/null
+++ b/src/cryptocurrency_portfolio__assets_selection.py
@@ -0,0 +1,188 @@
+#!/usr/bin/python3
+
+
+"""Crypto Currency Portfolio: Assets Selection.
+
+Description:
+ Crypto Currency Selection using monte Carlo simulation.
+"""
+
+# %% Import dependencies ----
+# core
+import os
+import gc
+
+# data science
+import pandas as pd
+import numpy as np
+from scipy.stats import norm
+
+# Cloud integration
+from azureml.core import Workspace, Dataset, VERSION as aml_version
+print(f'Azure ML SDK v{aml_version}')
+
+# network
+import ssl
+ssl._create_default_https_context = ssl._create_unverified_context
+
+# plots
+import matplotlib.pyplot as plt
+import seaborn as sns
+
+# show info about python env
+from IPython import sys_info
+print(sys_info())
+
+import warnings
+warnings.filterwarnings("ignore")
+
+
+# %% Set params ----
+symbols = ['BTCUSDT', 'ETHUSDT', 'BNBUSDT', 'SOLUSDT', 'MATICUSDT', 'UNIUSDT']
+
+n_days = int(252) # US market has 252 trading days in a year
+n_iterations = int(1e4)
+
+
+
+# %% Load quotes ----
+def get_quotes(symbol: str) -> pd.DataFrame:
+ df = pd.read_csv(f'https://www.cryptodatadownload.com/cdd/Binance_{symbol}_d.csv', skiprows=[0])
+ df = df.set_index('date')
+ df = df.sort_values(by = 'date')
+
+ return df[['symbol', 'open', 'high', 'low', 'close']]
+
+quotes_data = [get_quotes(s) for s in symbols]
+# row-wise union:
+# pd.concat([get_quotes(s) for s in symbols])
+# column-wise:
+# pd.concat(list1, axis=1, ignore_index=False)
+
+
+btcusdt_df = quotes_data[0]
+
+pd.concat([
+ btcusdt_df['close'].head(5),
+ btcusdt_df['close'].tail(5)
+])
+
+
+
+# %% Calculate Return
+def get_returns(close_prices) -> pd.Series:
+ return (close_prices/close_prices.shift()) - 1
+
+
+btcusdt_df['diff'] = btcusdt_df['close'].diff()
+btcusdt_df['return'] = get_returns(btcusdt_df['close'])
+
+btcusdt_df[['close', 'diff', 'return']].tail(10)
+
+
+
+# %% Calculate LogReturn
+def get_log_returns(return_prices) -> pd.Series:
+ return np.log(1 + return_prices)
+
+btcusdt_df['log_return'] = btcusdt_df['return'].apply(lambda x: get_log_returns(x))
+btcusdt_df[['close', 'diff', 'return', 'log_return']].tail(10)
+
+
+
+# %% Simulate possible LogReturns
+
+def get_simulated_returns(log_returns: pd.Series, n_days: int, n_iterations: int) -> pd.Series:
+ u = log_returns.mean()
+ var = log_returns.var()
+ stdev = log_returns.std()
+
+ drift = u - (0.5*var)
+ Z = norm.ppf(np.random.rand(n_days, n_iterations))
+
+ return np.exp(drift + stdev*Z)
+
+
+btcusd_logreturns = btcusdt_df['log_return'].dropna()
+btcusd_simulated_returns = get_simulated_returns(btcusd_logreturns, n_days, n_iterations)
+
+assert(
+ btcusd_simulated_returns.shape == (n_days, n_iterations)
+)
+
+
+
+# %% Monte carlo simulation functions ----
+def get_breakeven_prob(predicted, threshold = 0):
+ """
+ This function calculated the probability of a stock being above a certain threshhold, which can be defined as a value (final stock price) or return rate (percentage change)
+ """
+ predicted0 = predicted.iloc[0,0]
+ predicted = predicted.iloc[-1]
+ predList = list(predicted)
+
+ over = [(i*100)/predicted0 for i in predList if ((i-predicted0)*100)/predicted0 >= threshold]
+ less = [(i*100)/predicted0 for i in predList if ((i-predicted0)*100)/predicted0 < threshold]
+
+ return (len(over)/(len(over) + len(less)))
+
+
+def monte_carlo_simulation(simulated_returns: pd.Series, last_actual_price: float, n_days: int, plot=True):
+ # Create empty matrix
+ price_list = np.zeros_like(simulated_returns)
+
+ # Put the last actual price in the first row of matrix
+ price_list[0] = last_actual_price
+
+ # Calculate the price of each day
+ for t in range(1, n_days):
+ price_list[t] = price_list[t-1]*simulated_returns[t]
+
+ # Plot
+ if plot == True:
+ x = pd.DataFrame(price_list).iloc[-1]
+ fig, ax = plt.subplots(1, 2, figsize=(14,4))
+ sns.distplot(x, ax=ax[0])
+ sns.distplot(x, hist_kws={'cumulative': True}, kde_kws={'cumulative': True}, ax=ax[1])
+ plt.xlabel('Stock Price')
+ plt.show()
+
+
+ print(f"Investment period: {n_days-1}")
+ print(f"Expected Value: ${round(pd.DataFrame(price_list).iloc[-1].mean(),2)}")
+ print(f"Return: {round(100*(pd.DataFrame(price_list).iloc[-1].mean()-price_list[0,1])/pd.DataFrame(price_list).iloc[-1].mean(),2)}%")
+ print(f"Probability of Breakeven: {get_breakeven_prob(pd.DataFrame(price_list))}")
+
+ return pd.DataFrame(price_list)
+
+
+# %% Run Monte carlo simulation and estimate result
+
+simulated_prices_df = monte_carlo_simulation(
+ btcusd_simulated_returns,
+ quotes_data[0]['close'].tail(1),
+ n_days)
+
+
+plt.figure(figsize=(10,6))
+plt.plot(simulated_prices_df.iloc[:, 1:10])
+plt.show()
+
+
+
+# %% Monte Carlo simulation pipeline for multiple tokens ----
+
+n_iterations = int(1e4) #! WARN: set simulations number
+
+returns_data = [get_returns(df['close']) for df in quotes_data]
+log_returns_data = [get_log_returns(r) for r in returns_data]
+simulated_returns_data = [get_simulated_returns(lr, n_days, n_iterations) for lr in log_returns_data]
+
+
+for i in range(len(simulated_returns_data)):
+ print(f'Starting Monte-Carlo simulation for {symbols[i]} symbol...')
+ prices_ms = monte_carlo_simulation(simulated_returns_data[i], quotes_data[i]['close'].tail(1), n_days, plot=True)
+
+ plt.figure(figsize=(10,6))
+ plt.plot(prices_ms.iloc[:, 1:50])
+ plt.show()
diff --git a/src/fx_currencies_analysis.Rmd b/src/fx_currencies_analysis.Rmd
new file mode 100755
index 0000000..71d3269
--- /dev/null
+++ b/src/fx_currencies_analysis.Rmd
@@ -0,0 +1,248 @@
+---
+title: "Currencies Analysis"
+date: "`r format(Sys.time(), '%d %B, %Y')`"
+output:
+ github_document:
+ toc: false
+ toc_depth: 2
+ fig_width: 9
+ fig_height: 9
+---
+
+```{r setup, include=FALSE}
+knitr::opts_chunk$set(echo = T, warning = F)
+```
+
+***Analysis price of the my list of currencies.***
+
+## Prepare
+
+Install packages and set environment :earth_asia:
+
+`install.packages("azuremlsdk")`
+
+```{r set_envinroment, message=FALSE}
+options(max.print = 1e3, scipen = 999, width = 1e2)
+options(stringsAsFactors = F)
+
+suppressPackageStartupMessages({
+ library(dplyr)
+ library(tidyr)
+
+ library(lubridate)
+ library(stringr)
+
+ library(gt)
+ library(tidyverse)
+ library(glue)
+
+ library(ggplot2)
+
+ library(azuremlsdk)
+})
+```
+
+
+```{r set_params}
+.azureml_dataset_name <- "Currencies"
+```
+
+
+Connect to Azure ML workspace:
+
+```{r azureml_connect}
+ws <- azuremlsdk::load_workspace_from_config()
+sprintf(
+ "%s workspace located in %s region", ws$name, ws$location
+)
+```
+
+
+## Load dataset
+
+WARNING: I used `currency exchange rates` data from [Kaggle Dataset](https://www.kaggle.com/datasets/dhruvildave/currency-exchange-rates):
+
+```{r get_azure_dataset}
+currencies_ds <- azuremlsdk::get_dataset_by_name(ws, name = .azureml_dataset_name)
+
+sprintf(
+ "Dataset name: %s. %s",
+ currencies_ds$name,
+ currencies_ds$description
+)
+```
+
+Get `USD/RUB` top higher rates:
+
+```{r prepare_dataframe}
+quotes_df <- currencies_ds$to_pandas_dataframe()
+
+# ~ 20 years, 150 currencies and 1.5M rows
+
+quotes_df %>%
+ filter(slug == "USD/RUB") %>%
+ select(-slug) %>%
+ top_n(10) %>%
+
+ gt() %>%
+ tab_header(
+ title = "USD/RUB Rate",
+ subtitle = glue("{min(quotes_df$date)} to {max(quotes_df$date)}")
+ ) %>%
+ fmt_date(
+ columns = date,
+ date_style = 6
+ ) %>%
+ fmt_number(
+ columns = c(open, high, low, close)
+ )
+```
+
+## Preprocessing data
+
+Calculate `Return` and `Log Return` for last 10 years:
+
+```{r preprocessing}
+quotes_df %<>%
+ transmute(
+ symbol = slug,
+ price = close,
+ date
+ ) %>%
+
+ filter(
+ str_detect(symbol, "USD/") &
+ date > max(date) - lubridate::years(10)
+ ) %>%
+
+ filter(!(symbol == "USD/RUB" & price < 1)) %>%
+
+ arrange(date) %>%
+ group_by(symbol) %>%
+
+ mutate(
+ return = c(NA_real_, diff(price))/lag(price),
+ log_return = log(1 + return)
+ ) %>%
+ na.omit
+```
+
+## Discover Data
+
+Calculate statistics and `volatility`:
+
+```{r discover}
+quotes_stats <- quotes_df %>%
+
+ summarise(
+ max_price = max(price),
+ min_price = min(price),
+ last_price = last(price),
+ max_min_rate = max(price)/min(price),
+ volatility = sd(log_return)
+ )
+
+quotes_stats %>%
+ mutate(
+ `100x Volatility` = volatility*100
+ ) %>%
+ arrange(volatility) %>%
+ select(-volatility) %>%
+
+ gt() %>%
+ tab_header(
+ title = "The Least and The Most Volatile Currencies",
+ subtitle = glue("{min(quotes_df$date)} to {max(quotes_df$date)}")
+ ) %>%
+ fmt_number(
+ columns = c(max_price, min_price, max_min_rate, last_price, `100x Volatility`)
+ )
+```
+My broker trades the following pairs:
+
+```{r}
+symbols <- c(
+ 'RUB',
+ 'EUR', 'GBP', 'CHF', 'CNY', 'HKD', 'JPY', 'SEK', 'SGD', 'AUD',
+ 'AED', 'KZT', 'BYN', 'TRY', 'MXN'
+)
+
+symbols <- str_c("USD", symbols, sep = "/")
+
+
+quotes_stats %>%
+ filter(
+ symbol %in% symbols
+ ) %>%
+ mutate(
+ `100x Volatility` = volatility*100
+ ) %>%
+ arrange(volatility) %>%
+ select(-volatility) %>%
+
+ gt() %>%
+ tab_header(
+ title = "The Most Promised Currencies",
+ subtitle = glue("{min(quotes_df$date)} to {max(quotes_df$date)}")
+ ) %>%
+ fmt_number(
+ columns = c(max_price, min_price, last_price, max_min_rate, `100x Volatility`)
+ )
+
+```
+Plot exchange rate for out favorites:
+
+Define low risk symbols:
+
+```{r}
+usdrub_vol <- quotes_stats %>% filter(symbol == "USD/RUB") %>% pull(volatility)
+
+low_risk_symbols <- quotes_stats %>%
+ filter(
+ symbol %in% symbols &
+ volatility <= usdrub_vol
+ ) %>%
+ pull(symbol) %>%
+ unique
+
+cat(
+ sprintf(
+ "['%s']",
+ paste(low_risk_symbols, collapse = "', '")
+))
+```
+
+
+```{r}
+jumper_symbols <- quotes_stats %>% filter(max_min_rate > 2) %>% pull(symbol)
+
+quotes_df %>%
+ filter(symbol %in% low_risk_symbols) %>%
+ mutate(
+ jumper = if_else(symbol %in% jumper_symbols, "High risk currencies", "Low risk currencies")
+ ) %>%
+ group_by(symbol) %>%
+ mutate(R = cumsum(return)) %>%
+
+ ggplot +
+ geom_line(aes(x = date, y = R, color = symbol)) +
+ scale_y_continuous(labels = scales::percent_format(accuracy = 1)) +
+
+ facet_grid(jumper ~ ., scales = "free") +
+
+ labs(
+ title = "Currencies Exchange Rates", subtitle = "Return of Investment for last 10 years",
+ x = "", y = "Return of Investment",
+ caption = currencies_ds$description) +
+ theme_minimal() +
+
+ theme(
+ legend.position = "top", legend.title = element_blank(),
+ plot.caption = element_text(size = 8)
+ )
+```
+
+
+
+
+
diff --git a/src/fx_currencies_analysis.md b/src/fx_currencies_analysis.md
new file mode 100644
index 0000000..4d5d04a
--- /dev/null
+++ b/src/fx_currencies_analysis.md
@@ -0,0 +1,947 @@
+Currencies Analysis
+================
+04 April, 2022
+
+***Analysis price of the my list of currencies.***
+
+## Prepare
+
+Install packages and set environment :earth asia:
+
+`install.packages("azuremlsdk")`
+
+``` r
+options(max.print = 1e3, scipen = 999, width = 1e2)
+options(stringsAsFactors = F)
+
+suppressPackageStartupMessages({
+ library(dplyr)
+ library(tidyr)
+
+ library(lubridate)
+ library(stringr)
+
+ library(gt)
+ library(tidyverse)
+ library(glue)
+
+ library(ggplot2)
+
+ library(azuremlsdk)
+})
+```
+
+``` r
+.azureml_dataset_name <- "Currencies"
+```
+
+Connect to Azure ML workspace:
+
+``` r
+ws <- azuremlsdk::load_workspace_from_config()
+sprintf(
+ "%s workspace located in %s region", ws$name, ws$location
+)
+```
+
+ ## [1] "portf-opt-ws workspace located in westeurope region"
+
+## Load dataset
+
+WARNING: I used `currency exchange rates` data from [Kaggle
+Dataset](https://www.kaggle.com/datasets/dhruvildave/currency-exchange-rates):
+
+``` r
+currencies_ds <- azuremlsdk::get_dataset_by_name(ws, name = .azureml_dataset_name)
+
+sprintf(
+ "Dataset name: %s. %s",
+ currencies_ds$name,
+ currencies_ds$description
+)
+```
+
+ ## [1] "Dataset name: Currencies. Source: https://www.kaggle.com/datasets/dhruvildave/currency-exchange-rates"
+
+Get `USD/RUB` top higher rates:
+
+``` r
+quotes_df <- currencies_ds$to_pandas_dataframe()
+
+# ~ 20 years, 150 currencies and 1.5M rows
+
+quotes_df %>%
+ filter(slug == "USD/RUB") %>%
+ select(-slug) %>%
+ top_n(10) %>%
+
+ gt() %>%
+ tab_header(
+ title = "USD/RUB Rate",
+ subtitle = glue("{min(quotes_df$date)} to {max(quotes_df$date)}")
+ ) %>%
+ fmt_date(
+ columns = date,
+ date_style = 6
+ ) %>%
+ fmt_number(
+ columns = c(open, high, low, close)
+ )
+```
+
+ ## Selecting by close
+
+
+
+
+
+
+ date |
+ open |
+ high |
+ low |
+ close |
+
+
+
+ Jan 21, 2016 |
+82.06 |
+85.82 |
+82.06 |
+81.82 |
+ Jan 22, 2016 |
+80.61 |
+81.26 |
+77.94 |
+82.90 |
+ Jan 26, 2016 |
+81.54 |
+82.16 |
+78.33 |
+79.84 |
+ Feb 3, 2016 |
+79.56 |
+79.75 |
+77.87 |
+79.71 |
+ Feb 10, 2016 |
+79.39 |
+79.49 |
+77.65 |
+79.59 |
+ Feb 12, 2016 |
+79.36 |
+79.74 |
+78.59 |
+79.77 |
+ Mar 19, 2020 |
+80.92 |
+82.07 |
+79.24 |
+80.92 |
+ Mar 23, 2020 |
+79.72 |
+81.34 |
+79.49 |
+79.84 |
+ Mar 31, 2020 |
+79.59 |
+79.69 |
+77.66 |
+79.59 |
+ Nov 3, 2020 |
+80.55 |
+80.57 |
+79.05 |
+80.52 |
+
+
+
+
+
+
+## Preprocessing data
+
+Calculate `Return` and `Log Return` for last 10 years:
+
+``` r
+quotes_df %<>%
+ transmute(
+ symbol = slug,
+ price = close,
+ date
+ ) %>%
+
+ filter(
+ str_detect(symbol, "USD/") &
+ date > max(date) - lubridate::years(10)
+ ) %>%
+
+ filter(!(symbol == "USD/RUB" & price < 1)) %>%
+
+ arrange(date) %>%
+ group_by(symbol) %>%
+
+ mutate(
+ return = c(NA_real_, diff(price))/lag(price),
+ log_return = log(1 + return)
+ ) %>%
+ na.omit
+```
+
+## Discover Data
+
+Calculate statistics and `volatility`:
+
+``` r
+quotes_stats <- quotes_df %>%
+
+ summarise(
+ max_price = max(price),
+ min_price = min(price),
+ last_price = last(price),
+ max_min_rate = max(price)/min(price),
+ volatility = sd(log_return)
+ )
+
+quotes_stats %>%
+ mutate(
+ `100x Volatility` = volatility*100
+ ) %>%
+ arrange(volatility) %>%
+ select(-volatility) %>%
+
+ gt() %>%
+ tab_header(
+ title = "The Least and The Most Volatile Currencies",
+ subtitle = glue("{min(quotes_df$date)} to {max(quotes_df$date)}")
+ ) %>%
+ fmt_number(
+ columns = c(max_price, min_price, max_min_rate, last_price, `100x Volatility`)
+ )
+```
+
+
+
+
+
+
+ symbol |
+ max_price |
+ min_price |
+ last_price |
+ max_min_rate |
+ 100x Volatility |
+
+
+
+ USD/AED |
+3.67 |
+3.67 |
+3.67 |
+1.00 |
+0.01 |
+ USD/HKD |
+7.85 |
+7.75 |
+7.79 |
+1.01 |
+0.03 |
+ USD/KWD |
+0.31 |
+0.27 |
+0.30 |
+1.16 |
+0.16 |
+ USD/CNY |
+7.18 |
+6.03 |
+6.47 |
+1.19 |
+0.23 |
+ USD/DJF |
+177.72 |
+172.00 |
+177.50 |
+1.03 |
+0.28 |
+ USD/SGD |
+1.46 |
+1.20 |
+1.34 |
+1.21 |
+0.33 |
+ USD/SAR |
+3.77 |
+3.30 |
+3.75 |
+1.14 |
+0.39 |
+ USD/GTQ |
+7.89 |
+7.04 |
+7.73 |
+1.12 |
+0.41 |
+ USD/ILS |
+4.07 |
+3.13 |
+3.20 |
+1.30 |
+0.45 |
+ USD/TTD |
+6.78 |
+5.93 |
+6.76 |
+1.14 |
+0.47 |
+ USD/CAD |
+1.46 |
+0.97 |
+1.26 |
+1.51 |
+0.47 |
+ USD/MYR |
+4.49 |
+2.96 |
+4.16 |
+1.52 |
+0.50 |
+ USD/DKK |
+7.15 |
+5.18 |
+6.30 |
+1.38 |
+0.51 |
+ USD/EUR |
+0.96 |
+0.70 |
+0.85 |
+1.38 |
+0.51 |
+ USD/CRC |
+619.70 |
+478.54 |
+619.70 |
+1.29 |
+0.53 |
+ USD/PHP |
+54.23 |
+39.75 |
+49.71 |
+1.36 |
+0.54 |
+ USD/INR |
+77.57 |
+45.70 |
+73.29 |
+1.70 |
+0.54 |
+ USD/RON |
+4.54 |
+2.93 |
+4.18 |
+1.55 |
+0.55 |
+ USD/JPY |
+125.63 |
+75.74 |
+109.90 |
+1.66 |
+0.55 |
+ USD/GBP |
+0.87 |
+0.58 |
+0.73 |
+1.49 |
+0.55 |
+ USD/JMD |
+153.88 |
+83.37 |
+150.53 |
+1.85 |
+0.56 |
+ USD/MKD |
+58.92 |
+42.07 |
+51.98 |
+1.40 |
+0.58 |
+ USD/MDL |
+20.31 |
+11.09 |
+17.58 |
+1.83 |
+0.61 |
+ USD/BDT |
+84.72 |
+72.39 |
+84.72 |
+1.17 |
+0.62 |
+ USD/AUD |
+1.74 |
+0.93 |
+1.37 |
+1.88 |
+0.63 |
+ USD/SEK |
+10.44 |
+6.29 |
+8.62 |
+1.66 |
+0.64 |
+ USD/CHF |
+1.03 |
+0.79 |
+0.92 |
+1.31 |
+0.64 |
+ USD/CZK |
+26.03 |
+16.75 |
+21.67 |
+1.55 |
+0.64 |
+ USD/BWP |
+12.19 |
+6.58 |
+11.12 |
+1.85 |
+0.66 |
+ USD/NZD |
+1.78 |
+1.13 |
+1.43 |
+1.57 |
+0.66 |
+ USD/THB |
+36.43 |
+28.07 |
+32.45 |
+1.30 |
+0.67 |
+ USD/LKR |
+199.43 |
+106.22 |
+199.43 |
+1.88 |
+0.67 |
+ USD/KRW |
+1,262.93 |
+999.83 |
+1,165.89 |
+1.26 |
+0.70 |
+ USD/RSD |
+118.47 |
+70.05 |
+99.29 |
+1.69 |
+0.70 |
+ USD/UYU |
+45.31 |
+18.08 |
+42.53 |
+2.51 |
+0.71 |
+ USD/PLN |
+4.28 |
+2.87 |
+3.86 |
+1.49 |
+0.72 |
+ USD/HUF |
+338.26 |
+188.61 |
+294.66 |
+1.79 |
+0.74 |
+ USD/MUR |
+42.55 |
+26.50 |
+42.55 |
+1.61 |
+0.79 |
+ USD/MXN |
+25.34 |
+11.98 |
+20.14 |
+2.11 |
+0.80 |
+ USD/NIO |
+35.13 |
+22.05 |
+35.00 |
+1.59 |
+0.84 |
+ USD/KZT |
+454.34 |
+174.15 |
+427.18 |
+2.61 |
+0.84 |
+ USD/QAR |
+3.90 |
+3.00 |
+3.64 |
+1.30 |
+0.95 |
+ USD/TRY |
+8.78 |
+1.71 |
+8.38 |
+5.12 |
+0.97 |
+ USD/ZAR |
+19.25 |
+6.98 |
+14.66 |
+2.76 |
+0.99 |
+ USD/RUB |
+82.90 |
+28.79 |
+73.50 |
+2.88 |
+1.05 |
+ USD/ZMW |
+22.64 |
+5.11 |
+15.82 |
+4.43 |
+1.06 |
+ USD/BRL |
+5.89 |
+1.58 |
+5.19 |
+3.72 |
+1.08 |
+ USD/ARS |
+97.70 |
+4.10 |
+97.70 |
+23.85 |
+1.11 |
+ USD/TND |
+3.06 |
+1.37 |
+2.79 |
+2.23 |
+1.17 |
+ USD/BGN |
+1.87 |
+1.21 |
+1.66 |
+1.55 |
+1.28 |
+ USD/EGP |
+19.60 |
+5.83 |
+15.65 |
+3.37 |
+1.29 |
+ USD/NOK |
+11.76 |
+5.36 |
+8.66 |
+2.19 |
+1.31 |
+ USD/PEN |
+4.11 |
+2.38 |
+4.07 |
+1.72 |
+1.34 |
+ USD/BYN |
+3.08 |
+0.51 |
+2.51 |
+6.04 |
+1.37 |
+ USD/MAD |
+10.29 |
+7.89 |
+8.95 |
+1.30 |
+1.43 |
+ USD/UAH |
+33.50 |
+7.80 |
+26.92 |
+4.30 |
+1.83 |
+ USD/SDG |
+451.00 |
+1.39 |
+440.03 |
+324.46 |
+5.96 |
+ USD/BND |
+1.43 |
+0.66 |
+1.34 |
+2.18 |
+6.29 |
+ USD/XOF |
+647.00 |
+58.00 |
+555.47 |
+11.16 |
+6.44 |
+ USD/IDR |
+16,504.80 |
+892.00 |
+14,370.00 |
+18.50 |
+6.58 |
+ USD/HNL |
+24.90 |
+3.00 |
+23.83 |
+8.30 |
+8.14 |
+ USD/MZN |
+78.49 |
+3.30 |
+63.11 |
+23.78 |
+8.77 |
+ USD/ETB |
+45.23 |
+1.00 |
+45.06 |
+45.23 |
+9.26 |
+ USD/TWD |
+33.73 |
+1.80 |
+27.77 |
+18.72 |
+9.86 |
+ USD/PKR |
+168.15 |
+2.00 |
+165.63 |
+84.07 |
+12.69 |
+ USD/UZS |
+10,653.20 |
+83.00 |
+10,646.89 |
+128.35 |
+12.93 |
+ USD/GHS |
+573.00 |
+1.00 |
+5.98 |
+573.00 |
+13.74 |
+ USD/ISK |
+147.04 |
+2.00 |
+126.65 |
+73.52 |
+16.09 |
+ USD/PGK |
+3.51 |
+0.29 |
+3.51 |
+11.89 |
+16.18 |
+ USD/MMK |
+1,642.00 |
+6.23 |
+1,642.00 |
+263.65 |
+16.59 |
+ USD/CLP |
+867.50 |
+5.00 |
+782.21 |
+173.50 |
+18.70 |
+ USD/SZL |
+1,189.00 |
+1.07 |
+14.91 |
+1,111.21 |
+18.79 |
+ USD/XPF |
+119.35 |
+1.00 |
+100.90 |
+119.35 |
+19.32 |
+ USD/SOS |
+1,670.00 |
+6.00 |
+571.00 |
+278.33 |
+29.77 |
+ USD/MWK |
+812.43 |
+1.00 |
+804.55 |
+812.43 |
+30.83 |
+ USD/NGN |
+412.50 |
+1.00 |
+411.00 |
+412.50 |
+31.65 |
+ USD/VND |
+23,631.00 |
+21.00 |
+22,775.00 |
+1,125.29 |
+34.09 |
+ USD/COP |
+4,174.75 |
+3.67 |
+3,805.25 |
+1,136.67 |
+37.09 |
+ USD/IQD |
+1,578.00 |
+10.00 |
+1,458.00 |
+157.80 |
+37.10 |
+ USD/MGA |
+3,931.18 |
+0.30 |
+3,808.00 |
+12,910.29 |
+46.29 |
+ USD/SLL |
+10,250.50 |
+1.00 |
+10,250.50 |
+10,250.50 |
+47.59 |
+
+
+
+
+
+
+My broker trades the following pairs:
+
+``` r
+symbols <- c(
+ 'RUB',
+ 'EUR', 'GBP', 'CHF', 'CNY', 'HKD', 'JPY', 'SEK', 'SGD', 'AUD',
+ 'AED', 'KZT', 'BYN', 'TRY', 'MXN'
+)
+
+symbols <- str_c("USD", symbols, sep = "/")
+
+
+quotes_stats %>%
+ filter(
+ symbol %in% symbols
+ ) %>%
+ mutate(
+ `100x Volatility` = volatility*100
+ ) %>%
+ arrange(volatility) %>%
+ select(-volatility) %>%
+
+ gt() %>%
+ tab_header(
+ title = "The Most Promised Currencies",
+ subtitle = glue("{min(quotes_df$date)} to {max(quotes_df$date)}")
+ ) %>%
+ fmt_number(
+ columns = c(max_price, min_price, last_price, max_min_rate, `100x Volatility`)
+ )
+```
+
+
+
+
+
+
+
+ symbol |
+ max_price |
+ min_price |
+ last_price |
+ max_min_rate |
+ 100x Volatility |
+
+
+
+ USD/AED |
+3.67 |
+3.67 |
+3.67 |
+1.00 |
+0.01 |
+ USD/HKD |
+7.85 |
+7.75 |
+7.79 |
+1.01 |
+0.03 |
+ USD/CNY |
+7.18 |
+6.03 |
+6.47 |
+1.19 |
+0.23 |
+ USD/SGD |
+1.46 |
+1.20 |
+1.34 |
+1.21 |
+0.33 |
+ USD/EUR |
+0.96 |
+0.70 |
+0.85 |
+1.38 |
+0.51 |
+ USD/JPY |
+125.63 |
+75.74 |
+109.90 |
+1.66 |
+0.55 |
+ USD/GBP |
+0.87 |
+0.58 |
+0.73 |
+1.49 |
+0.55 |
+ USD/AUD |
+1.74 |
+0.93 |
+1.37 |
+1.88 |
+0.63 |
+ USD/SEK |
+10.44 |
+6.29 |
+8.62 |
+1.66 |
+0.64 |
+ USD/CHF |
+1.03 |
+0.79 |
+0.92 |
+1.31 |
+0.64 |
+ USD/MXN |
+25.34 |
+11.98 |
+20.14 |
+2.11 |
+0.80 |
+ USD/KZT |
+454.34 |
+174.15 |
+427.18 |
+2.61 |
+0.84 |
+ USD/TRY |
+8.78 |
+1.71 |
+8.38 |
+5.12 |
+0.97 |
+ USD/RUB |
+82.90 |
+28.79 |
+73.50 |
+2.88 |
+1.05 |
+ USD/BYN |
+3.08 |
+0.51 |
+2.51 |
+6.04 |
+1.37 |
+
+
+
+
+
+
+Plot exchange rate for out favorites:
+
+Define low risk symbols:
+
+``` r
+usdrub_vol <- quotes_stats %>% filter(symbol == "USD/RUB") %>% pull(volatility)
+
+low_risk_symbols <- quotes_stats %>%
+ filter(
+ symbol %in% symbols &
+ volatility <= usdrub_vol
+ ) %>%
+ pull(symbol) %>%
+ unique
+
+cat(
+ sprintf(
+ "['%s']",
+ paste(low_risk_symbols, collapse = "', '")
+))
+```
+
+ ## ['USD/AED', 'USD/AUD', 'USD/CHF', 'USD/CNY', 'USD/EUR', 'USD/GBP', 'USD/HKD', 'USD/JPY', 'USD/KZT', 'USD/MXN', 'USD/RUB', 'USD/SEK', 'USD/SGD', 'USD/TRY']
+
+``` r
+jumper_symbols <- quotes_stats %>% filter(max_min_rate > 2) %>% pull(symbol)
+
+quotes_df %>%
+ filter(symbol %in% low_risk_symbols) %>%
+ mutate(
+ jumper = if_else(symbol %in% jumper_symbols, "High risk currencies", "Low risk currencies")
+ ) %>%
+ group_by(symbol) %>%
+ mutate(R = cumsum(return)) %>%
+
+ ggplot +
+ geom_line(aes(x = date, y = R, color = symbol)) +
+ scale_y_continuous(labels = scales::percent_format(accuracy = 1)) +
+
+ facet_grid(jumper ~ ., scales = "free") +
+
+ labs(
+ title = "Currencies Exchange Rates", subtitle = "Return of Investment for last 10 years",
+ x = "", y = "Return of Investment",
+ caption = currencies_ds$description) +
+ theme_minimal() +
+
+ theme(
+ legend.position = "top", legend.title = element_blank(),
+ plot.caption = element_text(size = 8)
+ )
+```
+
+![](fx_currencies_analysis_files/figure-gfm/unnamed-chunk-3-1.png)
diff --git a/src/fx_currencies_analysis_files/figure-gfm/unnamed-chunk-3-1.png b/src/fx_currencies_analysis_files/figure-gfm/unnamed-chunk-3-1.png
new file mode 100644
index 0000000..ad48797
Binary files /dev/null and b/src/fx_currencies_analysis_files/figure-gfm/unnamed-chunk-3-1.png differ
diff --git a/src/fx_currency_portfolio__assets_selection.ipynb b/src/fx_currency_portfolio__assets_selection.ipynb
new file mode 100644
index 0000000..4465908
--- /dev/null
+++ b/src/fx_currency_portfolio__assets_selection.ipynb
@@ -0,0 +1,1318 @@
+{
+ "cells": [
+ {
+ "cell_type": "code",
+ "execution_count": 1,
+ "source": [
+ "# core\n",
+ "import sys\n",
+ "import warnings\n",
+ "from IPython import sys_info\n",
+ "\n",
+ "# data science\n",
+ "import pandas as pd\n",
+ "import numpy as np\n",
+ "from scipy.stats import norm\n",
+ "\n",
+ "# Cloud integration\n",
+ "from azureml.core import Workspace, Dataset, ComputeTarget, VERSION as aml_version\n",
+ "print(f'Azure ML SDK v{aml_version}')\n",
+ "# plots\n",
+ "import matplotlib.pyplot as plt\n",
+ "import seaborn as sns\n",
+ "\n",
+ "# show info about python env\n",
+ "print(sys_info())\n",
+ "warnings.filterwarnings(\"ignore\")"
+ ],
+ "outputs": [
+ {
+ "output_type": "stream",
+ "name": "stdout",
+ "text": [
+ "Azure ML SDK v1.36.0\n",
+ "{'commit_hash': '980a41b',\n",
+ " 'commit_source': 'installation',\n",
+ " 'default_encoding': 'utf-8',\n",
+ " 'ipython_path': '/anaconda/envs/azureml_py38/lib/python3.8/site-packages/IPython',\n",
+ " 'ipython_version': '7.31.0',\n",
+ " 'os_name': 'posix',\n",
+ " 'platform': 'Linux-5.4.0-1063-azure-x86_64-with-glibc2.10',\n",
+ " 'sys_executable': '/anaconda/envs/azureml_py38/bin/python',\n",
+ " 'sys_platform': 'linux',\n",
+ " 'sys_version': '3.8.12 | packaged by conda-forge | (default, Oct 12 2021, '\n",
+ " '21:57:06) \\n'\n",
+ " '[GCC 9.4.0]'}\n"
+ ]
+ }
+ ],
+ "metadata": {}
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 2,
+ "source": [
+ "n_days = int(252) # US market has 252 trading days in a year\n",
+ "n_simulations = int(1e4) # number of Monte-Carlo simulations\n",
+ "\n",
+ "# The most promised currencies (copy this list from fx_currencies_analysis.Rmd)\n",
+ "symbols = [\n",
+ " 'USD/AED', 'USD/AUD', 'USD/CHF', 'USD/CNY', 'USD/EUR', 'USD/GBP', \n",
+ " 'USD/HKD', 'USD/JPY', 'USD/KZT', 'USD/MXN', 'USD/RUB', 'USD/SEK', 'USD/SGD'\n",
+ "]"
+ ],
+ "outputs": [],
+ "metadata": {}
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 3,
+ "source": [
+ "ws = Workspace.from_config()\n",
+ "print(f\"Connected to *{ws.get_details()['friendlyName']}* workspace in *{ws.get_details()['location']}*.\")\n",
+ "\n",
+ "print('Compute Targets:')\n",
+ "for compute_name in ws.compute_targets:\n",
+ " compute = ws.compute_targets[compute_name]\n",
+ " print('\\t', compute.name, ':', compute.type)\n",
+ "\n",
+ "# > htop"
+ ],
+ "outputs": [
+ {
+ "output_type": "stream",
+ "name": "stdout",
+ "text": [
+ "Connected to *portf-opt-ws* workspace in *westeurope*.\n",
+ "Compute Targets:\n",
+ "\t demo-vm : ComputeInstance\n"
+ ]
+ }
+ ],
+ "metadata": {}
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 4,
+ "source": [
+ "currencies_ds = Dataset.get_by_name(ws, name='Currencies')\n",
+ "currencies_ds.to_pandas_dataframe()\n",
+ "\n",
+ "print(f'Dataset name: {currencies_ds.name}. Description: {currencies_ds.description}.')\n",
+ "print(f'Size of Azure ML dataset object: {sys.getsizeof(currencies_ds)} bytes.')"
+ ],
+ "outputs": [
+ {
+ "output_type": "stream",
+ "name": "stdout",
+ "text": [
+ "Dataset name: Currencies. Description: Source: https://www.kaggle.com/datasets/dhruvildave/currency-exchange-rates.\n",
+ "Size of Azure ML dataset object: 48 bytes.\n"
+ ]
+ }
+ ],
+ "metadata": {}
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 5,
+ "source": [
+ "quotes_df = (currencies_ds\n",
+ " # materialize\n",
+ " .to_pandas_dataframe()\n",
+ " # define format\n",
+ " .rename(columns={'slug': 'symbol'})\n",
+ " .loc[:, ['symbol', 'date', 'close']]\n",
+ " # filter\n",
+ " .query(\"symbol in @symbols\")\n",
+ " .query(\"date > '2012-01-01'\")\n",
+ " # set time index\n",
+ " .set_index('date')\n",
+ " .sort_values(by='date'))\n",
+ "\n",
+ "quotes_df"
+ ],
+ "outputs": [
+ {
+ "output_type": "execute_result",
+ "data": {
+ "text/html": [
+ "\n",
+ "\n",
+ "
\n",
+ " \n",
+ " \n",
+ " | \n",
+ " symbol | \n",
+ " close | \n",
+ "
\n",
+ " \n",
+ " date | \n",
+ " | \n",
+ " | \n",
+ "
\n",
+ " \n",
+ " \n",
+ " \n",
+ " 2012-01-02 | \n",
+ " USD/GBP | \n",
+ " 0.644660 | \n",
+ "
\n",
+ " \n",
+ " 2012-01-02 | \n",
+ " USD/EUR | \n",
+ " 0.771600 | \n",
+ "
\n",
+ " \n",
+ " 2012-01-02 | \n",
+ " USD/KZT | \n",
+ " 175.919998 | \n",
+ "
\n",
+ " \n",
+ " 2012-01-02 | \n",
+ " USD/RUB | \n",
+ " 31.988701 | \n",
+ "
\n",
+ " \n",
+ " 2012-01-02 | \n",
+ " USD/SEK | \n",
+ " 6.876300 | \n",
+ "
\n",
+ " \n",
+ " ... | \n",
+ " ... | \n",
+ " ... | \n",
+ "
\n",
+ " \n",
+ " 2021-08-30 | \n",
+ " USD/AUD | \n",
+ " 1.369950 | \n",
+ "
\n",
+ " \n",
+ " 2021-08-30 | \n",
+ " USD/SGD | \n",
+ " 1.344480 | \n",
+ "
\n",
+ " \n",
+ " 2021-08-30 | \n",
+ " USD/EUR | \n",
+ " 0.847000 | \n",
+ "
\n",
+ " \n",
+ " 2021-08-30 | \n",
+ " USD/AED | \n",
+ " 3.672800 | \n",
+ "
\n",
+ " \n",
+ " 2021-08-30 | \n",
+ " USD/MXN | \n",
+ " 20.135000 | \n",
+ "
\n",
+ " \n",
+ "
\n",
+ "
32459 rows × 2 columns
\n",
+ "
"
+ ],
+ "text/plain": [
+ " symbol close\n",
+ "date \n",
+ "2012-01-02 USD/GBP 0.644660\n",
+ "2012-01-02 USD/EUR 0.771600\n",
+ "2012-01-02 USD/KZT 175.919998\n",
+ "2012-01-02 USD/RUB 31.988701\n",
+ "2012-01-02 USD/SEK 6.876300\n",
+ "... ... ...\n",
+ "2021-08-30 USD/AUD 1.369950\n",
+ "2021-08-30 USD/SGD 1.344480\n",
+ "2021-08-30 USD/EUR 0.847000\n",
+ "2021-08-30 USD/AED 3.672800\n",
+ "2021-08-30 USD/MXN 20.135000\n",
+ "\n",
+ "[32459 rows x 2 columns]"
+ ]
+ },
+ "metadata": {},
+ "execution_count": 5
+ }
+ ],
+ "metadata": {}
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 6,
+ "source": [
+ "quotes_df.groupby('symbol')['close'].agg(['count', 'last'])"
+ ],
+ "outputs": [
+ {
+ "output_type": "execute_result",
+ "data": {
+ "text/html": [
+ "\n",
+ "\n",
+ "
\n",
+ " \n",
+ " \n",
+ " | \n",
+ " count | \n",
+ " last | \n",
+ "
\n",
+ " \n",
+ " symbol | \n",
+ " | \n",
+ " | \n",
+ "
\n",
+ " \n",
+ " \n",
+ " \n",
+ " USD/AED | \n",
+ " 2499 | \n",
+ " 3.672800 | \n",
+ "
\n",
+ " \n",
+ " USD/AUD | \n",
+ " 2496 | \n",
+ " 1.369950 | \n",
+ "
\n",
+ " \n",
+ " USD/CHF | \n",
+ " 2494 | \n",
+ " 0.916910 | \n",
+ "
\n",
+ " \n",
+ " USD/CNY | \n",
+ " 2496 | \n",
+ " 6.465800 | \n",
+ "
\n",
+ " \n",
+ " USD/EUR | \n",
+ " 2496 | \n",
+ " 0.847000 | \n",
+ "
\n",
+ " \n",
+ " USD/GBP | \n",
+ " 2497 | \n",
+ " 0.726610 | \n",
+ "
\n",
+ " \n",
+ " USD/HKD | \n",
+ " 2496 | \n",
+ " 7.786940 | \n",
+ "
\n",
+ " \n",
+ " USD/JPY | \n",
+ " 2497 | \n",
+ " 109.902000 | \n",
+ "
\n",
+ " \n",
+ " USD/KZT | \n",
+ " 2497 | \n",
+ " 427.179993 | \n",
+ "
\n",
+ " \n",
+ " USD/MXN | \n",
+ " 2500 | \n",
+ " 20.135000 | \n",
+ "
\n",
+ " \n",
+ " USD/RUB | \n",
+ " 2497 | \n",
+ " 73.503998 | \n",
+ "
\n",
+ " \n",
+ " USD/SEK | \n",
+ " 2497 | \n",
+ " 8.618400 | \n",
+ "
\n",
+ " \n",
+ " USD/SGD | \n",
+ " 2497 | \n",
+ " 1.344480 | \n",
+ "
\n",
+ " \n",
+ "
\n",
+ "
"
+ ],
+ "text/plain": [
+ " count last\n",
+ "symbol \n",
+ "USD/AED 2499 3.672800\n",
+ "USD/AUD 2496 1.369950\n",
+ "USD/CHF 2494 0.916910\n",
+ "USD/CNY 2496 6.465800\n",
+ "USD/EUR 2496 0.847000\n",
+ "USD/GBP 2497 0.726610\n",
+ "USD/HKD 2496 7.786940\n",
+ "USD/JPY 2497 109.902000\n",
+ "USD/KZT 2497 427.179993\n",
+ "USD/MXN 2500 20.135000\n",
+ "USD/RUB 2497 73.503998\n",
+ "USD/SEK 2497 8.618400\n",
+ "USD/SGD 2497 1.344480"
+ ]
+ },
+ "metadata": {},
+ "execution_count": 6
+ }
+ ],
+ "metadata": {}
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 7,
+ "source": [
+ "usdrub_df = quotes_df[quotes_df.symbol == 'USD/RUB']\n",
+ "usdrub_df"
+ ],
+ "outputs": [
+ {
+ "output_type": "execute_result",
+ "data": {
+ "text/html": [
+ "\n",
+ "\n",
+ "
\n",
+ " \n",
+ " \n",
+ " | \n",
+ " symbol | \n",
+ " close | \n",
+ "
\n",
+ " \n",
+ " date | \n",
+ " | \n",
+ " | \n",
+ "
\n",
+ " \n",
+ " \n",
+ " \n",
+ " 2012-01-02 | \n",
+ " USD/RUB | \n",
+ " 31.988701 | \n",
+ "
\n",
+ " \n",
+ " 2012-01-03 | \n",
+ " USD/RUB | \n",
+ " 31.988701 | \n",
+ "
\n",
+ " \n",
+ " 2012-01-04 | \n",
+ " USD/RUB | \n",
+ " 31.749001 | \n",
+ "
\n",
+ " \n",
+ " 2012-01-05 | \n",
+ " USD/RUB | \n",
+ " 31.751301 | \n",
+ "
\n",
+ " \n",
+ " 2012-01-06 | \n",
+ " USD/RUB | \n",
+ " 32.002602 | \n",
+ "
\n",
+ " \n",
+ " ... | \n",
+ " ... | \n",
+ " ... | \n",
+ "
\n",
+ " \n",
+ " 2021-08-24 | \n",
+ " USD/RUB | \n",
+ " 74.080498 | \n",
+ "
\n",
+ " \n",
+ " 2021-08-25 | \n",
+ " USD/RUB | \n",
+ " 73.761497 | \n",
+ "
\n",
+ " \n",
+ " 2021-08-26 | \n",
+ " USD/RUB | \n",
+ " 73.769997 | \n",
+ "
\n",
+ " \n",
+ " 2021-08-27 | \n",
+ " USD/RUB | \n",
+ " 74.267502 | \n",
+ "
\n",
+ " \n",
+ " 2021-08-30 | \n",
+ " USD/RUB | \n",
+ " 73.503998 | \n",
+ "
\n",
+ " \n",
+ "
\n",
+ "
2497 rows × 2 columns
\n",
+ "
"
+ ],
+ "text/plain": [
+ " symbol close\n",
+ "date \n",
+ "2012-01-02 USD/RUB 31.988701\n",
+ "2012-01-03 USD/RUB 31.988701\n",
+ "2012-01-04 USD/RUB 31.749001\n",
+ "2012-01-05 USD/RUB 31.751301\n",
+ "2012-01-06 USD/RUB 32.002602\n",
+ "... ... ...\n",
+ "2021-08-24 USD/RUB 74.080498\n",
+ "2021-08-25 USD/RUB 73.761497\n",
+ "2021-08-26 USD/RUB 73.769997\n",
+ "2021-08-27 USD/RUB 74.267502\n",
+ "2021-08-30 USD/RUB 73.503998\n",
+ "\n",
+ "[2497 rows x 2 columns]"
+ ]
+ },
+ "metadata": {},
+ "execution_count": 7
+ }
+ ],
+ "metadata": {}
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 8,
+ "source": [
+ "def calc_returns(close_prices: pd.Series) -> pd.Series:\n",
+ " \"\"\"Calculate Investment Return\"\"\"\n",
+ " return (close_prices/close_prices.shift()) - 1\n",
+ "\n",
+ "\n",
+ "usdrub_df['diff'] = usdrub_df['close'].diff()\n",
+ "usdrub_df['return'] = calc_returns(usdrub_df['close'])\n",
+ "\n",
+ "usdrub_df[['close', 'diff', 'return']].tail(10)"
+ ],
+ "outputs": [
+ {
+ "output_type": "execute_result",
+ "data": {
+ "text/html": [
+ "\n",
+ "\n",
+ "
\n",
+ " \n",
+ " \n",
+ " | \n",
+ " close | \n",
+ " diff | \n",
+ " return | \n",
+ "
\n",
+ " \n",
+ " date | \n",
+ " | \n",
+ " | \n",
+ " | \n",
+ "
\n",
+ " \n",
+ " \n",
+ " \n",
+ " 2021-08-17 | \n",
+ " 73.282700 | \n",
+ " 0.132599 | \n",
+ " 0.001813 | \n",
+ "
\n",
+ " \n",
+ " 2021-08-18 | \n",
+ " 73.595703 | \n",
+ " 0.313004 | \n",
+ " 0.004271 | \n",
+ "
\n",
+ " \n",
+ " 2021-08-19 | \n",
+ " 73.843498 | \n",
+ " 0.247795 | \n",
+ " 0.003367 | \n",
+ "
\n",
+ " \n",
+ " 2021-08-20 | \n",
+ " 74.275101 | \n",
+ " 0.431602 | \n",
+ " 0.005845 | \n",
+ "
\n",
+ " \n",
+ " 2021-08-23 | \n",
+ " 74.195099 | \n",
+ " -0.080002 | \n",
+ " -0.001077 | \n",
+ "
\n",
+ " \n",
+ " 2021-08-24 | \n",
+ " 74.080498 | \n",
+ " -0.114601 | \n",
+ " -0.001545 | \n",
+ "
\n",
+ " \n",
+ " 2021-08-25 | \n",
+ " 73.761497 | \n",
+ " -0.319000 | \n",
+ " -0.004306 | \n",
+ "
\n",
+ " \n",
+ " 2021-08-26 | \n",
+ " 73.769997 | \n",
+ " 0.008499 | \n",
+ " 0.000115 | \n",
+ "
\n",
+ " \n",
+ " 2021-08-27 | \n",
+ " 74.267502 | \n",
+ " 0.497505 | \n",
+ " 0.006744 | \n",
+ "
\n",
+ " \n",
+ " 2021-08-30 | \n",
+ " 73.503998 | \n",
+ " -0.763504 | \n",
+ " -0.010280 | \n",
+ "
\n",
+ " \n",
+ "
\n",
+ "
"
+ ],
+ "text/plain": [
+ " close diff return\n",
+ "date \n",
+ "2021-08-17 73.282700 0.132599 0.001813\n",
+ "2021-08-18 73.595703 0.313004 0.004271\n",
+ "2021-08-19 73.843498 0.247795 0.003367\n",
+ "2021-08-20 74.275101 0.431602 0.005845\n",
+ "2021-08-23 74.195099 -0.080002 -0.001077\n",
+ "2021-08-24 74.080498 -0.114601 -0.001545\n",
+ "2021-08-25 73.761497 -0.319000 -0.004306\n",
+ "2021-08-26 73.769997 0.008499 0.000115\n",
+ "2021-08-27 74.267502 0.497505 0.006744\n",
+ "2021-08-30 73.503998 -0.763504 -0.010280"
+ ]
+ },
+ "metadata": {},
+ "execution_count": 8
+ }
+ ],
+ "metadata": {}
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 9,
+ "source": [
+ "def calc_log_returns(return_prices: pd.Series) -> pd.Series:\n",
+ " \"\"\"Calculate Log Return\"\"\"\n",
+ " return np.log(1 + return_prices)\n",
+ "\n",
+ "usdrub_df['log_return'] = usdrub_df['return'].apply(lambda x: calc_log_returns(x))\n",
+ "\n",
+ "usdrub_df[['close', 'diff', 'return', 'log_return']].tail(10)"
+ ],
+ "outputs": [
+ {
+ "output_type": "execute_result",
+ "data": {
+ "text/html": [
+ "\n",
+ "\n",
+ "
\n",
+ " \n",
+ " \n",
+ " | \n",
+ " close | \n",
+ " diff | \n",
+ " return | \n",
+ " log_return | \n",
+ "
\n",
+ " \n",
+ " date | \n",
+ " | \n",
+ " | \n",
+ " | \n",
+ " | \n",
+ "
\n",
+ " \n",
+ " \n",
+ " \n",
+ " 2021-08-17 | \n",
+ " 73.282700 | \n",
+ " 0.132599 | \n",
+ " 0.001813 | \n",
+ " 0.001811 | \n",
+ "
\n",
+ " \n",
+ " 2021-08-18 | \n",
+ " 73.595703 | \n",
+ " 0.313004 | \n",
+ " 0.004271 | \n",
+ " 0.004262 | \n",
+ "
\n",
+ " \n",
+ " 2021-08-19 | \n",
+ " 73.843498 | \n",
+ " 0.247795 | \n",
+ " 0.003367 | \n",
+ " 0.003361 | \n",
+ "
\n",
+ " \n",
+ " 2021-08-20 | \n",
+ " 74.275101 | \n",
+ " 0.431602 | \n",
+ " 0.005845 | \n",
+ " 0.005828 | \n",
+ "
\n",
+ " \n",
+ " 2021-08-23 | \n",
+ " 74.195099 | \n",
+ " -0.080002 | \n",
+ " -0.001077 | \n",
+ " -0.001078 | \n",
+ "
\n",
+ " \n",
+ " 2021-08-24 | \n",
+ " 74.080498 | \n",
+ " -0.114601 | \n",
+ " -0.001545 | \n",
+ " -0.001546 | \n",
+ "
\n",
+ " \n",
+ " 2021-08-25 | \n",
+ " 73.761497 | \n",
+ " -0.319000 | \n",
+ " -0.004306 | \n",
+ " -0.004315 | \n",
+ "
\n",
+ " \n",
+ " 2021-08-26 | \n",
+ " 73.769997 | \n",
+ " 0.008499 | \n",
+ " 0.000115 | \n",
+ " 0.000115 | \n",
+ "
\n",
+ " \n",
+ " 2021-08-27 | \n",
+ " 74.267502 | \n",
+ " 0.497505 | \n",
+ " 0.006744 | \n",
+ " 0.006721 | \n",
+ "
\n",
+ " \n",
+ " 2021-08-30 | \n",
+ " 73.503998 | \n",
+ " -0.763504 | \n",
+ " -0.010280 | \n",
+ " -0.010334 | \n",
+ "
\n",
+ " \n",
+ "
\n",
+ "
"
+ ],
+ "text/plain": [
+ " close diff return log_return\n",
+ "date \n",
+ "2021-08-17 73.282700 0.132599 0.001813 0.001811\n",
+ "2021-08-18 73.595703 0.313004 0.004271 0.004262\n",
+ "2021-08-19 73.843498 0.247795 0.003367 0.003361\n",
+ "2021-08-20 74.275101 0.431602 0.005845 0.005828\n",
+ "2021-08-23 74.195099 -0.080002 -0.001077 -0.001078\n",
+ "2021-08-24 74.080498 -0.114601 -0.001545 -0.001546\n",
+ "2021-08-25 73.761497 -0.319000 -0.004306 -0.004315\n",
+ "2021-08-26 73.769997 0.008499 0.000115 0.000115\n",
+ "2021-08-27 74.267502 0.497505 0.006744 0.006721\n",
+ "2021-08-30 73.503998 -0.763504 -0.010280 -0.010334"
+ ]
+ },
+ "metadata": {},
+ "execution_count": 9
+ }
+ ],
+ "metadata": {}
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 10,
+ "source": [
+ "def calc_simulated_returns(log_returns: pd.Series, n_days: int, n_iterations: int) -> pd.Series:\n",
+ " \"\"\"Calculate Simulated Return\"\"\"\n",
+ "\n",
+ " u = log_returns.mean()\n",
+ " var = log_returns.var()\n",
+ " stdev = log_returns.std()\n",
+ "\n",
+ " drift = u - (0.5*var)\n",
+ " Z = norm.ppf(np.random.rand(n_days, n_iterations))\n",
+ "\n",
+ " return np.exp(drift + stdev*Z)\n",
+ "\n",
+ "\n",
+ "usdrub_simulated_returns = calc_simulated_returns(\n",
+ " usdrub_df['log_return'].dropna(),\n",
+ " n_days,\n",
+ " n_simulations)\n",
+ "\n",
+ "assert(\n",
+ " usdrub_simulated_returns.shape == (n_days, n_simulations)\n",
+ " and (usdrub_simulated_returns > 0).all()\n",
+ " and (usdrub_simulated_returns < 2).all()\n",
+ ")"
+ ],
+ "outputs": [],
+ "metadata": {}
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 11,
+ "source": [
+ "def get_breakeven_prob(pred, risk_free_rate: float = 0.02) -> pd.Series:\n",
+ " \"\"\"\n",
+ " Calculation of the probability of a stock being above a certain threshold,\n",
+ " which can be defined as a value (final stock price) or return rate (percentage change).\n",
+ " \"\"\"\n",
+ "\n",
+ " init_pred = pred.iloc[0, 0]\n",
+ "\n",
+ " last_pred = pred.iloc[-1]\n",
+ " pred_list = list(last_pred)\n",
+ "\n",
+ " over = [(p*100)/init_pred for p in pred_list if ((p-init_pred)*100)/init_pred >= risk_free_rate]\n",
+ " less = [(p*100)/init_pred for p in pred_list if ((p-init_pred)*100)/init_pred < risk_free_rate]\n",
+ "\n",
+ " return len(over)/(len(over) + len(less))\n",
+ "\n",
+ "\n",
+ "def evaluate_simulation(simulated_returns: pd.Series, last_actual_price: float, n_days: int, plot: bool = True) -> pd.DataFrame:\n",
+ " \"\"\"\n",
+ " Evaluate Monte-Carlo simulations result\n",
+ " \"\"\"\n",
+ "\n",
+ " # Create empty matrix\n",
+ " price_list = np.zeros_like(simulated_returns)\n",
+ "\n",
+ " # Put the last actual price in the first row,\n",
+ " # and calculate the price of each day\n",
+ " price_list[0] = last_actual_price\n",
+ " for t in range(1, n_days):\n",
+ " price_list[t] = price_list[(t - 1)]*simulated_returns[t]\n",
+ "\n",
+ " # convert to temp dataframe\n",
+ " price_df = pd.DataFrame(price_list)\n",
+ "\n",
+ " # Plot\n",
+ " if plot == True:\n",
+ " x = price_df.iloc[-1]\n",
+ " fig, ax = plt.subplots(1, 2, figsize=(14, 4))\n",
+ " sns.distplot(x, ax=ax[0])\n",
+ " sns.distplot(x, hist_kws={'cumulative': True}, kde_kws={'cumulative':True}, ax=ax[1])\n",
+ " plt.xlabel('Stock Price')\n",
+ " plt.show()\n",
+ "\n",
+ " print('Results:')\n",
+ " print(f'\\tInvestment period: {n_days-1} days')\n",
+ " print(f'\\tExpected Value: {round(price_df.iloc[-1].mean(), 2)} per USD')\n",
+ " print(f'\\tReturn: {round(100*(price_df.iloc[-1].mean() - price_list[0,1])/price_df.iloc[-1].mean(), 2)}%')\n",
+ " print(f'\\tProbability of Breakeven: {get_breakeven_prob(price_df)}')\n",
+ "\n",
+ " return price_df"
+ ],
+ "outputs": [],
+ "metadata": {}
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 12,
+ "source": [
+ "usdrub_mc_simulation_df = evaluate_simulation(\n",
+ " usdrub_simulated_returns,\n",
+ " last_actual_price=usdrub_df['close'].tail(1),\n",
+ " n_days=n_days)\n",
+ "\n",
+ "\n",
+ "plt.figure(figsize=(10, 6))\n",
+ "plt.plot(usdrub_mc_simulation_df.sample(10, axis='columns'))\n",
+ "plt.title('USD/RUB Price Simulation')\n",
+ "plt.xlabel('Days')\n",
+ "plt.ylabel('RUB per $1')\n",
+ "plt.ylim(10, 300)\n",
+ "plt.show()"
+ ],
+ "outputs": [
+ {
+ "output_type": "display_data",
+ "data": {
+ "image/png": "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",
+ "text/plain": [
+ "