Data Science For Financial Markets
Data Science For Financial Markets
Table of Contents
Introduction
Essential Libraries
yfinance
Quantstats
PyPortfolioOpt
TA
1 | Getting Started
Daily Returns
Cumulative Returns
Histograms
Kurtosis
Skewness
Standard Deviation
Pairplots and Correlation Matrix
Sharpe Ratio
Initial Conclusions
What is a Portfolio?
Optimizing Portfolio
Prior
Views
Confidences
Technical Indicators
Moving Averages
Bollinger Bands
Fundamental Indicators
Debt-to-Equity Ratio
Dividend Yield
4 | Backtesting
RSI Backtesting
Hourly Data
Daily Data
Weekly Data
Hourly Data
Daily Data
Weekly Data
Conclusion
Introduction
Data Science is a rapidly growing field that combines the power of
statistical and computational techniques to extract valuable insights
and knowledge from data. It brings together multiple disciplines such
as mathematics, statistics, computer science, and domain-specific
knowledge to create a multi-faceted approach to understanding
complex data patterns.
Essential Libraries
yfinance
yfinance is probably the most popular Python library to extract data
from financial markets! It allows you to obtain and analyze historical
market data from Yahoo!Finance. It offers an easy-to-use API that
allows users to fetch data for any publicly traded company, index, ETF,
crypto and forex.
yfinance also provides tools for adjusting the data for dividends and
splits, as well as for visualizing the data in different ways. Its simple
interface and reliable data, makes it an excellent tool for analysis of
financial data and explains why it is one of the most used library for
traders and investors alike.
You can copy the code cell below in any Python environment to install
it.
Quantstats
PyPortfolioOpt
In the code cell below, you find how to install PyPortfolioOpt in your
Python environment.
TA
The TA (Technical Analysis) library is a powerful tool for conducting
technical analysis using Python. It provides a wide range of technical
indicators, such as moving averages, Bollinger bands, MACD, and the
Relative Strength Index to analyze market trends, momentum, and
volatility.
Here's how you can install the TA library in your own Python
environment:
Collecting ta
Downloading ta-0.10.2.tar.gz (25 kB)
Preparing metadata (setup.py) ... done
Requirement already satisfied: numpy in /opt/conda/lib/python3.7/site-packages (fr
om ta) (1.21.6)
Requirement already satisfied: pandas in /opt/conda/lib/python3.7/site-packages (f
rom ta) (1.3.5)
Requirement already satisfied: python-dateutil>=2.7.3 in /opt/conda/lib/python3.7/
site-packages (from pandas->ta) (2.8.2)
Requirement already satisfied: pytz>=2017.3 in /opt/conda/lib/python3.7/site-packa
ges (from pandas->ta) (2022.7.1)
Requirement already satisfied: six>=1.5 in /opt/conda/lib/python3.7/site-packages
(from python-dateutil>=2.7.3->pandas->ta) (1.15.0)
Building wheels for collected packages: ta
Building wheel for ta (setup.py) ... done
Created wheel for ta: filename=ta-0.10.2-py3-none-any.whl size=29088 sha256=9c1b
92d61d6086f71a83f13e01c328020b7e2d8d83f5e843c13e0e8d298960ba
Stored in directory: /root/.cache/pip/wheels/31/31/f1/f2ff471bbc5b84a4b973698cee
cdd453ae043971791adc3431
Successfully built ta
Installing collected packages: ta
Successfully installed ta-0.10.2
WARNING: Running pip as the 'root' user can result in broken permissions and confl
icting behaviour with the system package manager. It is recommended to use a virtu
al environment instead: https://pip.pypa.io/warnings/venv
Now that you've had a brief introduction to the most essential financial
libraries in this notebook, we can move on to importing all the specific
libraries we'll be using.
# Data visualization
import matplotlib.pyplot as plt
import seaborn as sns
import plotly.express as px
import plotly.graph_objects as go
from plotly.subplots import make_subplots
# Financial data
import quantstats as qs
import ta
import yfinance as yf
(CVXPY) Mar 21 07:42:46 PM: Encountered unexpected exception importing solver GLO
P:
RuntimeError('Unrecognized new version of ortools (9.5.2237). Expected < 9.5.0.Ple
ase open a feature request on cvxpy to enable support for this version.')
(CVXPY) Mar 21 07:42:46 PM: Encountered unexpected exception importing solver PDL
P:
RuntimeError('Unrecognized new version of ortools (9.5.2237). Expected < 9.5.0.Ple
ase open a feature request on cvxpy to enable support for this version.')
1 | Getting Started
Daily Returns
The first thing we're going to look at is the daily returns. A stock's daily
return is the percentual change in price over a single day. You calculate
it by subtracting the difference between the stock's closing price on one
day and its closing price the day before, dividing the result by the
closing of the day before, and multiplying it by 100.
This shows that the stock increased in value by 2% over the course of
one day. On the other hand, if the stock had closed at 98 dollars on
Tuesday, the daily return would be calculated as:
Which means that the stock has decreased in value by 2% over the
course of one day.
Daily returns are relevant for investors because they provide a quick
way to check the performance of a stock over a short period.
With Quantstats, it's possible to plot daily returns charts, which are
graphical representations of the daily percentage changes in stocks,
allowing investors to visualize the ups and downs of the stock's daily
performance over time and extract information on volatility and
consistency of returns.
In [6]: # Getting daily returns for 4 different US stocks in the same time window
aapl = qs.utils.download_returns('AAPL')
aapl = aapl.loc['2010-07-01':'2023-02-10']
tsla = qs.utils.download_returns('TSLA')
tsla = tsla.loc['2010-07-01':'2023-02-10']
dis = qs.utils.download_returns('DIS')
dis = dis.loc['2010-07-01':'2023-02-10']
amd = qs.utils.download_returns('AMD')
amd = amd.loc['2010-07-01':'2023-02-10']
We now have the daily returns from July 1st, 2010, to February 10th,
2023, for four different US stocks from distinct industries, Apple, Tesla,
The Walt Disney Company, and AMD.
We can now plot the daily returns chart for each of them using
Quantstats.
On the other hand, Disney's and Apple's stocks seem more stable and
predictable investment options at first glance.
Cumulative Returns
It's important to note that cumulative return takes into account the
effects of compounding, meaning that any gains from a previous period
are reinvested and contribute to additional gains in future periods,
which can result in a larger cumulative return than the simple average
of the individual returns over the specified period.
Below, we can see line charts displaying the cumulative return for each
one of the stocks we've downloaded since July, 2010.
Histograms
Through the analysis of the histograms, we can observe that most daily
returns are close to zero in the center of the distribution. However, it's
easy to see some extreme values that are distant from the mean, which
is the case of AMD, with daily returns of around 50%, indicating the
presence of outliers in the positive range of the distribution, in contrast
with the negative field where it seems to limit at about -20%.
Disney's stocks have more balanced returns with values ranging from
-15% to 15%, while most returns are closer to the mean.
In the image below, it's possible to see the difference between a negative
kurtosis on the left and a positive kurtosis on the right. The distribution
on the left displays a lower probability of extreme values and a lower
concentration of values around the mean, while the distribution on the
right shows a higher concentration of values near the mean, but also the
existence (and thus a higher probability of occurrence) of extreme
values.
The kurtosis values above show that all four stocks, Apple, Tesla, Walt
Disney, and Advanced Micro Devices, have high levels of kurtosis,
indicating a high concentration of observations in the tails of their daily
returns distributions, which suggests that all four stocks are subject to
high levels of volatility and risk, with considerate price fluctuations that
deviate significantly from their average returns.
However, AMD has the highest kurtosis, with a value of 17.125, which
indicates that AMD is subject to an extremely high level of volatility and
tail risk, with a large concentration of extreme price movements. On the
other hand, Disney has a kurtosis of 11.033, which is still higher than a
typical value for a normal distribution, but not as extreme as AMD's.
Skewness
2 3
μ3 (x)−3×μ(x)×σ (x)−μ (x)
skewness =
3
σ (x)
Where x represents the set of returns data, μ represents the mean of the
returns, and σ represents the standard deviation of the returns. This
formula results in a single numerical value that summarizes the
skewness of returns.
In [12]: # Measuring skewness with quantstats
print('\n')
print("Apple's skewness: ", qs.stats.skew(aapl).round(2))
print('\n')
print("Tesla's skewness: ", qs.stats.skew(tsla).round(2))
print('\n')
print("Walt Disney's skewness: ", qs.stats.skew(dis).round(3))
print('\n')
print("Advances Micro Devices' skewness: ", qs.stats.skew(amd).round(3))
Apple, Tesla, and Disney are just slightly skewed, and Disney's slight
skewness can be seen by looking at the range of the x-axis of its
histogram, where it is pretty much balanced between -15% and 15%.
AMD stocks are strongly skewed, which can also be easily identified by
looking at the range between -20% and 50% in its histogram. AMD has
a lot of outliers on the positive tail, which could've been a good thing for
those who bought its shares but it also suggests higher volatility and
risk to this investment.
Standard Deviation
1 N
2
σ = √ ∑ (xi − x̄)
N −1 i=1
Where x represents the set of returns data, x̄ is the mean of the returns
data, and N is the number of observations. Standard deviation enables
investors to assess the risk level and to compare the volatility of
different stocks. For instance, if two assets have similar average returns,
but one has a higher standard deviation, it is usually considered a
riskier investment. Hence, standard deviation serves as a useful tool in
helping investors to make informed decisions regarding their
investment choices and portfolio management.
Based on the values above, we can say that Apple and Disney are less
volatile than Tesla and AMD, suggesting that Apple and Disney are
safer investment options, exhibiting more stable price fluctuations in
the market.
Correlation analysis is not only useful for Long-Short strategies, but it's
also crucial to avoid systemic risk, which is described as the risk of the
breakdown of an entire system rather than simply the failure of
individual parts. To make it simple, if your portfolio has stocks that are
highly correlated, or are all in the same industry, if something happens
to that specific industry, all of your stocks may lose market value and it
can cause greater financial losses.
Date
The dataframe above has dates serving as the index and each stock is
represented as a column, displaying their respective returns for each
specific day. This dataframe will be used to calculate the correlation
between these stocks and to create a pairplot visualization.
In [15]: # Pairplots
sns.pairplot(merged_df, kind = 'reg')
plt.show()
In [16]: # Correlation Matrix
corr = merged_df.corr()
mask = np.zeros_like(corr, dtype=np.bool)
mask[np.triu_indices_from(mask)] = True
sns.heatmap(corr, annot=True, mask = mask)
plt.show()
The stronger correlation among the assets above is between Disney and
Apple. However, a correlation of 0.42 is not a strong one.
It's important to note that there is not any negative correlation among
the assets above, which indicates that none of them acts to limit losses.
In the financial market, a hedge is an investment position intended to
offset potential losses by investing in assets that may have a negative
correlation with the others in a portfolio. Many investors buy gold to
serve as protection for riskier investments, such as stocks, and when the
market as a whole goes into a bear market, the gold tends to increase in
value, limiting potential losses for the overall portfolio.
Beta and Alpha are two key metrics used in finance to evaluate the
performance of a stock relative to the overall market. Beta is a measure
of a stock's volatility compared to the market. A Beta of 1 means that the
stock is as volatile as the market, a Beta greater than 1 indicates higher
volatility than the market, and a Beta less than 1 suggests lower
volatility.
To determine Beta and Alpha, we require data from the SP500, which
acts as the benchmark, to fit a linear regression model between the
stocks and the index. This will enable us to extract the Beta and Alpha
values of the stocks.
Date
Out[17]:
2010-07-01 04:00:00 -0.003240
2010-07-02 04:00:00 -0.004662
2010-07-06 04:00:00 0.005359
2010-07-07 04:00:00 0.031331
2010-07-08 04:00:00 0.009413
...
2023-02-06 05:00:00 -0.006140
2023-02-07 05:00:00 0.012873
2023-02-08 05:00:00 -0.011081
2023-02-09 05:00:00 -0.008830
2023-02-10 05:00:00 0.002195
Name: Close, Length: 3176, dtype: float64
0 -0.003240
Out[19]:
1 -0.004662
2 0.005359
3 0.031331
4 0.009413
...
3171 -0.006140
3172 0.012873
3173 -0.011081
3174 -0.008830
3175 0.002195
Name: Close, Length: 3176, dtype: float64
0 -0.012126
Out[20]:
1 -0.006197
2 0.006844
3 0.040381
4 -0.002242
...
3171 -0.017929
3172 0.019245
3173 -0.017653
3174 -0.006912
3175 0.002456
Name: Close, Length: 3176, dtype: float64
linreg = LinearRegression().fit(X, y)
beta = linreg.coef_[0]
alpha = linreg.intercept_
print('\n')
print('AAPL beta: ', beta.round(3))
print('\nAAPL alpha: ', alpha.round(3))
linreg = LinearRegression().fit(X, y)
beta = linreg.coef_[0]
alpha = linreg.intercept_
print('\n')
print('TSLA beta: ', beta.round(3))
print('\nTSLA alpha: ', alpha.round(3))
In [23]: # Fitting linear relation among Walt Disney's returns and Benchmark
X = sp500_no_index.values.reshape(-1,1)
y = dis_no_index.values.reshape(-1,1)
linreg = LinearRegression().fit(X, y)
beta = linreg.coef_[0]
alpha = linreg.intercept_
print('\n')
print('Walt Disney Company beta: ', beta.round(3))
print('\nWalt Disney Company alpha: ', alpha.round(4))
linreg = LinearRegression().fit(X, y)
beta = linreg.coef_[0]
alpha = linreg.intercept_
print('\n')
print('AMD beta: ', beta.round(3))
print('\nAMD alpha: ', alpha.round(4))
AMD beta: [1.603]
Beta values for all the stocks are greater than 1, meaning that they are
more volatile than the benchmark and may offer higher returns, but
also come with greater risks. On the other hand, the alpha values for all
the stocks are small, close to zero, suggesting that there is little
difference between the expected returns and the risk-adjusted returns.
Sharpe Ratio
Apple and Tesla have the highest Sharpe ratios among the stocks
analyzed, 0.97 and 0.95, respectively, indicating that these investments
offer a better risk-return relationship. However, none of the stocks have
a Sharp ratio above 1, which may suggest that these investments'
average returns are beneath the risk-free rate of return.
It's important to note that the Sharpe ratio is an annual metric and,
since the beginning of 2022, the market, in general, has been bearish,
with prices going down over the past year.
Initial Conclusions
Some initial conclusions can be drawn via the analysis of the metrics
above:
. Apple and Tesla have the best Sharpe ratios, which indicates a better
risk-return relationship;
. Tesla has the highest returns of them all, but it's also more volatile
than Apple and Disney;
. Apple has higher returns and low volatility compared to the other
assets. It has the best Sharpe ratio, low beta, low standard deviation,
and low asymmetry of returns;
. AMD is the riskier and more volatile investment option of the four. Its
returns distribution is highly asymmetric, it has a high standard
deviation value and high beta;
. Disney stocks may be a good option for investors that are sensitive to
risk, considering they had a steady and stable return over the period.
It's possible to say that, from all the assets analyzed, Apple offers the
best risk-return relationship, with high rentability and lower risk than
the other options.
What is a Portfolio?
In [26]: weights = [0.25, 0.25, 0.25, 0.25] # Defining weights for each stock
portfolio = aapl*weights[0] + tsla*weights[1] + dis*weights[2] + amd*weights[3] # C
portfolio # Displaying portfolio's daily returns
Date
Out[26]:
2010-07-01 04:00:00 -0.020338
2010-07-02 04:00:00 -0.041286
2010-07-06 04:00:00 -0.040348
2010-07-07 04:00:00 0.028905
2010-07-08 04:00:00 0.026538
...
2023-02-06 05:00:00 -0.007087
2023-02-07 05:00:00 0.018110
2023-02-08 05:00:00 -0.001937
2023-02-09 05:00:00 -0.001783
2023-02-10 05:00:00 -0.022371
Name: Close, Length: 3176, dtype: float64
In [27]: # Generating report on portfolio performance from July 1st, 2010 to February 10th,
qs.reports.full(portfolio, benchmark = sp500)
Performance Metrics
Strategy Benchmark
------------------------- ---------- -----------
Start Period 2010-07-01 2010-07-01
End Period 2023-02-10 2023-02-10
Risk-Free Rate 0.0% 0.0%
Time in Market 100.0% 100.0%
Beta 1.28 -
Alpha 0.17 -
Correlation 73.94% -
Treynor Ratio 2682.16% -
None
5 Worst Drawdowns
Start Valley End Days Max Drawdown 99% Max Drawdown
Strategy Visualization
We have a range of metrics and plots available to look at. Firstly, the
Cumulative Return of the portfolio is higher than the benchmark, at
3,429.9% compared to 296.86% for the SP500. The Sharpe Ratio and
Sortino Ratio of the portfolio are also higher, indicating that it
generates better returns for the level of risk taken. In addition, the
portfolio has higher expected daily, monthly and annual returns than
the SP500, and its best day, month, and year outperforms the
benchmark's best day, month, and year.
While the portfolio has higher returns on its best day, month, and year,
it also has bigger losses on its worst day, month, and year compared to
the benchmark. The beta of 1.28 shows that the portfolio is about 28%
more volatile than the overall market, and its 73.94% correlation
indicates a strong positive relationship among the four stocks,
suggesting that they tend to move in the same direction, which could
increase the systemic risk of the portfolio.
Overall, the portfolio has generated impressive returns, but it also
comes with a higher degree of risk and volatility. This prompts the
question of whether or not it's possible to optimize the portfolio to
reduce risk and volatility while also increasing returns.
Optimizing Portfolio
[*********************100%***********************] 1 of 1 completed
[*********************100%***********************] 1 of 1 completed
[*********************100%***********************] 1 of 1 completed
[*********************100%***********************] 1 of 1 completed
Date
First, we need to have expected returns for each of the assets in the
portfolio. PyPortfolioOpt provides the expected_returns module, which
calculates expected returns for the assets by computing the arithmetic
mean of their daily percentage changes. The module assumes that daily
prices are available as input and produces expected annual returns as
output. More information on this topic is available here.
Secondly, we need to choose a risk model that quantifies the level of risk
in each asset. The most commonly used risk model is the covariance
matrix, which describes the volatilities of assets and the degree to which
they are co-dependent. Choosing an appropriate risk model is critical,
because it can help to reduce risk by making many uncorrelated bets.
PyPortfolioOpt offers a range of risk models to choose from, including
the annualized sample covariance matrix of daily returns,
semicovariance matrix, and exponentially-weighted covariance matrix.
Further information on risk models can be found here
In [32]: # Calculating the annualized expected returns and the annualized sample covariance
mu = expected_returns.mean_historical_return(df) #expected returns
S = risk_models.sample_cov(df) #Covariance matrix
aapl 0.268385
Out[33]:
tsla 0.475549
dis 0.114966
amd 0.209862
dtype: float64
Now that we have estimated the expected returns and the covariance
matrix, we can use these inputs for portfolio optimization.
Date
Out[36]:
2010-07-01 04:00:00 -0.031481
2010-07-02 04:00:00 -0.041054
2010-07-06 04:00:00 -0.042102
2010-07-07 04:00:00 0.022988
2010-07-08 04:00:00 0.029061
...
2023-02-06 05:00:00 -0.005359
2023-02-07 05:00:00 0.016701
2023-02-08 05:00:00 -0.005863
2023-02-09 05:00:00 0.003844
2023-02-10 05:00:00 -0.012936
Name: Close, Length: 3176, dtype: float64
In [37]: # Displaying new reports comparing the optimized portfolio to the first portfolio c
qs.reports.full(optimized_portfolio, benchmark = portfolio)
Performance Metrics
Strategy Benchmark
------------------------- ---------- -----------
Start Period 2010-07-01 2010-07-01
End Period 2023-02-10 2023-02-10
Risk-Free Rate 0.0% 0.0%
Time in Market 100.0% 100.0%
Beta 0.86 -
Alpha 0.07 -
Correlation 86.12% -
Treynor Ratio 5623.07% -
None
5 Worst Drawdowns
Start Valley End Days Max Drawdown 99% Max Drawdown
Strategy Visualization
Based on the report above, the optimized portfolio appears to have
performed better than the original portfolio. Here are some key
conclusions that can be drawn by looking at the metrics and plots in the
report:
. Win Rates: The optimized portfolio has slightly higher win rates for
win days, win months, win quarters, and win years, indicating that it
had a higher probability of generating positive returns over these
periods.
With this information, we can calculate the prior expected returns for
each stock based on its market capitalization,the delta, and the
covariance matrix S, which we've obtained before optimizing our
portfolio with the Markowitz Mean-Variance Model. These prior
expected returns gives us a starting point for the expected returns
before we incoporate any of our views as investors.
Views
These views must be specified in the vector Q and mapped into each
asset via the picking matrix P.
1. TSLA
2. AAPL
3. NVDA
4. MSFT
5. META
6. AMZN
7. AMD
8. HD
9. GOOGL
10. BRKa
And then consider two absolute views and two relative views, such as:
The views vector would be formed by taking the numbers above and
specifying them as below:
Q = np.array([0.20, -0.15, 0.10, 0.05]).reshape(-1,1)
The picking matrix would then be used to link the views of the 8
mentioned assets above to the universe of 10 assets, allowing us to
propagate our expectations into the model:
P = np.array([
[1,0,0,0,0,0,0,0,0,0],
[0,1,0,0,0,0,0,0,0,0],
[0,0,0,0,-1,0,0,1,0,0],
[0,0,0,-0.5,0,-0.5,0,0,0.5,0.5],
])
Confidences
AAPL 2518055124992
Out[39]:
TSLA 624150380544
DIS 176707338240
AMD 155483013120
Name: marketCap, dtype: int64
[*********************100%***********************] 1 of 1 completed
Date
Out[40]:
2010-07-01 1027.369995
2010-07-02 1022.580017
2010-07-06 1028.060059
2010-07-07 1060.270020
2010-07-08 1070.250000
...
2023-02-06 4111.080078
2023-02-07 4164.000000
2023-02-08 4117.859863
2023-02-09 4081.500000
2023-02-10 4090.459961
Name: Adj Close, Length: 3176, dtype: float64
3.3668161617990653
Out[41]:
AAPL 0.269523
Out[44]:
TSLA 0.384137
DIS 0.141240
AMD 0.293677
dtype: float64
Now that we have our prior estimates for each stock, we can now
provide the model our views on these stocks and our confidence levels
in our views.
OrderedDict([('AAPL', 0.63718),
Out[50]:
('TSLA', 0.18636),
('DIS', 0.01442),
('AMD', 0.16204)])
After obtaining prior expected returns and providing our views, as well
as our confidence levels, we have an optimized portfolio with the
following weights for each asset:
Apple: 62.59%
Tesla: 19.95%
Disney: 1.6%
AMD: 15.86%
Date
Out[52]:
2010-07-01 04:00:00 -0.021734
2010-07-02 04:00:00 -0.033732
2010-07-06 04:00:00 -0.030528
2010-07-07 04:00:00 0.030036
2010-07-08 04:00:00 0.019225
...
2023-02-06 05:00:00 -0.010763
2023-02-07 05:00:00 0.018628
2023-02-08 05:00:00 -0.008738
2023-02-09 05:00:00 -0.001324
2023-02-10 05:00:00 -0.012131
Name: Close, Length: 3176, dtype: float64
Beta 0.91 -
Alpha 0.04 -
Correlation 94.09% -
Treynor Ratio 4497.23% -
None
5 Worst Drawdowns
Start Valley End Days Max Drawdown 99% Max Drawdown
Strategy Visualization
By using the Black-Litterman Allocation Model, we were able to
improve our investment portfolio's performance metrics compared to
the original portfolio, where each asset was allocated a uniform weight
of 25%. The Black-Litterman optimized portfolio outperformed the
original portfolio in several key metrics. First, it generated higher
cumulative return and CAGR, indicating a stronger overall
performance. Additionally, the Sharpe and Sortino ratios were higher,
demonstrating greater risk-adjusted returns.
Technical analysis is a very popular method used for many traders and
investors to evaluate stocks and other assets based on historical price
and volume data. It is an approach used to identify trends, or lack of
trends, and help traders and investors to make decisions based on what
they believe the future price movements will be. The underlying
assumption of technical analysis is that past patterns and price
movements tend to repeat themselves, so those can be used to predict
future movements. Therefore, technical analysts examine charts and
look for opportunities in patterns and indicators.
Despite its popularity among traders, the use of technical analysis may
be controversial. Some critics argue that technical analysis relies too
heavily on subjective interpretations of chart patterns and that it lacks a
clear theoretical foundation. They also argue that technical analysis is
prone to false signals and that traders who rely on technical analysis
may miss out on important fundamental factors that can influence the
price of stocks.
It can also be said that, while humans tend to operate better with
fundamental analysis, as it requires a deep understanding of the
underlying factors that drive a company's value, computers may operate
better with technical analysis, as it relies heavily on quantitative data
that can be analyzed quickly and efficiently. An evidence to that is the
fact that the use of automated trading bots that trade based on technical
analysis has become increasingly popular in recent years. These bots
use algorithms to identify patterns and trends in price data and make
trades based on technical signals.
In Python, there are quite a few libraries that are suited for both
technical and fundamental analysis, and we're going to explore how to
use these indicators, not only in conventional ways but also to feed
machine learning models.
Technical Indicators
Moving Averages
EMA, on the other hand, gives more weight to recent price action. The
formula for calculating EMA involves using a multiplier that gives more
weight to the most recent price data. The formula for calculating a 9-
day EMA, for example, would be:
The multiplier used depends on the length of the period used. For a 9-
day EMA, the multiplier would be 2/(9+1) = 0.2 (rounded to one
decimal place).
Bollinger Bands
Usually, traders use these bands to identify possible buying and selling
entries. When the price touches or crosses below the lower band, it may
be considered oversold and a potential buy signal. Conversely, when the
price touches or crosses above the upper band, it may be considered
overbought and a potential sell signal.
To obtain the Bollinger Bands values, there are three important steps:
The RSI is another popular technical analysis tool used by traders and
analysts in financial markets. It measures the strength of price action
and can be used to identify possible buy and sell opportunities.
Average Gain
RS =
Average Loss
In order to obtain the average gain or loss, the difference between the
closing price of the current day and the previous day is taken. If that
difference is positive, then it is considered a gain, but if that difference
is negative, it is considered a loss.
The next step is to obtain the RSI using the RS value. The RSI ranges
from 0 to 100 and is plotted on a chart.
100
RSI = 100 −
1+RS
The RSI is also popularly used to identify divergences between the RSI
and price, which can indicate the loss of momentum, i.e. strenght, in the
current trend and suggest a possible reversal.
Average True Range (ATR)
The Average True Range (ATR) is yet another commong indicator used
to measure the volatility of price action and help traders to identify
potential buy and sell opportunities.
1.The difference between the current high and the previous close.
2. The difference between the current low and the previous close.
The difference between the current high and the current low.
Before obtaining the ATR, we need to first obtain the true range
through the following formula:
Where:
The ATR ranges from 0 to infinity, and the higher its value, the higher
may be the volatility in price action, while the opposite is true for a
lower ATR value.
Traders and analysts may use the ATR to set stop-loss and take-profit
levels, as well as to identify potential trend reversals. For example, if the
ATR value is increasing, it could indicate that a trend reversal is likely
to occur.
[*********************100%***********************] 1 of 1 completed
Date
# Plotting annotation
fig.add_annotation(text='Apple (AAPL)',
font=dict(color='white', size=40),
xref='paper', yref='paper',
x=0.5, y=0.65,
showarrow=False,
opacity=0.2)
# Configuring layout
fig.update_layout(title='AAPL Candlestick Chart From July 1st, 2010 to February 10t
yaxis=dict(title='Price (USD)'),
height=1000,
template = 'plotly_dark')
fig.show()
AAPL Candlestick Chart From July 1st, 2010 to February 10t
180
160
140
120
Price (USD)
100
80
App
Above, it's possible to see a daily candlestick chart containing price and
volume of Apple stocks traded from July 1st, 2010 to February 10th,
2023. The candlestick chart makes it easy to see information on the
opening, closing, high, and low prices of each trading day, as well as the
overall trend in the last 13 years.
The use of the 'Adj Close' column instead of the 'Close' column to plot
the chart above is due to the adjustment of historical prices for
dividends and stock splits. The 'Adj Close' value represents the closing
price adjusted for these factors, which allows for a more accurate
representation of the stock's true price over time.
The volume bars below the candlestick chart display the financial
volume of shares traded for each day.
Date
2022-
141.399994 148.720001 140.550003 148.029999 147.804321 111380900 146.624471 145.357
11-30
2022-
148.210007 149.130005 146.610001 148.309998 148.083893 71250400 146.916355 145.533
12-01
2022-
145.960007 148.000000 145.649994 147.809998 147.584656 65447400 147.050015 145.990
12-02
2022-
147.770004 150.919998 145.770004 146.630005 146.406464 68826400 146.921305 146.402
12-05
2022-
147.070007 147.300003 141.919998 142.910004 142.692139 64727200 146.075472 146.601
12-06
2022-
142.190002 143.369995 140.000000 140.940002 140.725143 69721100 145.005406 146.673
12-07
2022-
142.360001 143.520004 141.100006 142.649994 142.432526 62128300 144.490830 147.061
12-08
2022-
142.339996 145.570007 140.899994 142.160004 141.943283 76097000 143.981321 146.826
12-09
2022-
142.699997 144.500000 141.059998 144.490005 144.269730 70462700 144.039002 146.566
12-12
2022-
149.500000 149.970001 144.240005 145.470001 145.248230 93886200 144.280848 146.426
12-13
2022-
145.350006 146.660004 141.160004 143.210007 142.991684 82291200 144.023015 146.085
12-14
2022-
141.110001 141.800003 136.029999 136.500000 136.291901 98931900 142.476792 145.471
12-15
2022-
136.690002 137.649994 133.729996 134.509995 134.304932 160156900 140.842420 144.662
12-16
2022-
135.110001 135.199997 131.320007 132.369995 132.168198 79592600 139.107576 143.718
12-19
2022-
131.389999 133.250000 129.889999 132.300003 132.098312 77432800 137.705723 142.933
12-20
2022-
132.979996 136.809998 132.750000 135.449997 135.243500 85928000 137.213278 142.198
12-21
2022-
134.350006 134.559998 130.300003 132.229996 132.028412 77852100 136.176305 141.257
12-22
2022-
130.919998 132.419998 129.639999 131.860001 131.658981 63814900 135.272840 140.446
12-23
2022-
131.380005 131.410004 128.720001 130.029999 129.831772 69007800 134.184627 139.738
12-27
2022-
129.669998 131.029999 125.870003 126.040001 125.847855 85438400 132.517272 138.982
12-28
2022-
127.989998 130.479996 127.730003 129.610001 129.412415 75703700 131.896301 138.063
12-29
2022-
128.410004 129.949997 127.430000 129.929993 129.731918 77034200 131.463424 137.145
12-30
Open High Low Close Adj Close Volume EMA9 SMA
Date
2023-
130.279999 130.899994 124.169998 125.070000 124.879326 112117500 130.146605 136.010
01-03
2023-
126.889999 128.660004 125.080002 126.360001 126.167366 89113600 129.350757 134.998
01-04
2023-
127.129997 127.769997 124.760002 125.019997 124.829399 80962700 128.446485 134.105
01-05
2023-
126.010002 130.289993 124.889999 129.619995 129.422394 87754700 128.641667 133.540
01-06
2023-
130.470001 133.410004 129.889999 130.149994 129.951584 70790800 128.903650 132.916
01-09
2023-
130.259995 131.259995 128.119995 130.729996 130.530701 63896200 129.229060 132.345
01-10
2023-
131.250000 133.509995 130.460007 133.490005 133.286499 69458900 130.040548 131.796
01-11
2023-
133.880005 134.259995 131.440002 133.410004 133.206619 71379600 130.673762 131.194
01-12
2023-
132.029999 134.919998 131.660004 134.759995 134.554550 57809700 131.449920 130.772
01-13
2023-
134.830002 137.289993 134.130005 135.940002 135.732758 63646600 132.306487 130.744
01-17
2023-
136.820007 138.610001 135.029999 135.210007 135.003876 69672800 132.845965 130.779
01-18
2023-
134.080002 136.250000 133.770004 135.270004 135.063782 58280400 133.289528 130.924
01-19
2023-
135.279999 138.020004 134.220001 137.869995 137.659805 80223600 134.163584 131.202
01-20
2023-
138.119995 143.320007 137.899994 141.110001 140.894882 81760300 135.509843 131.484
01-23
2023-
140.309998 143.160004 140.300003 142.529999 142.312714 66435100 136.870418 131.998
01-24
2023-
140.889999 142.429993 138.809998 141.860001 141.643738 65799300 137.825082 132.498
01-25
2023-
143.169998 144.250000 141.899994 143.960007 143.740540 54105100 139.008173 133.193
01-26
2023-
143.160004 147.229996 143.080002 145.929993 145.707520 70555800 140.348042 134.186
01-27
2023-
144.960007 145.550003 142.850006 143.000000 142.781998 64015300 140.834833 134.855
01-30
2023-
142.699997 144.339996 142.279999 144.289993 144.070023 65874500 141.481871 135.572
01-31
2023-
143.970001 146.610001 141.320007 145.429993 145.208282 77663600 142.227154 136.588
02-01
2023-
148.899994 151.179993 148.169998 150.820007 150.590088 118339000 143.899740 137.809
02-02
Open High Low Close Adj Close Volume EMA9 SMA
Date
2023-
148.029999 157.380005 147.830002 154.500000 154.264465 154357300 145.972685 139.281
02-03
2023-
152.570007 153.100006 150.779999 151.729996 151.498688 69858300 147.077886 140.385
02-06
2023-
150.639999 155.229996 150.639999 154.649994 154.414230 83322600 148.545155 141.608
02-07
2023-
153.880005 154.580002 151.169998 151.919998 151.688400 64120100 149.173804 142.666
02-08
2023-
153.779999 154.330002 150.419998 150.869995 150.639999 56007100 149.467043 143.533
02-09
2023-
149.460007 151.339996 149.220001 151.009995 151.009995 57450700 149.775633 144.424
02-10
# Candlestick
fig.add_trace(go.Candlestick(x=aapl.index,
open=aapl['Open'],
high=aapl['High'],
low=aapl['Low'],
close=aapl['Adj Close'],
name='AAPL'),
row=1, col=1)
# Moving Averages
fig.add_trace(go.Scatter(x=aapl.index,
y=aapl['EMA9'],
mode='lines',
line=dict(color='#90EE90'),
name='EMA9'),
row=1, col=1)
fig.add_trace(go.Scatter(x=aapl.index,
y=aapl['SMA20'],
mode='lines',
line=dict(color='yellow'),
name='SMA20'),
row=1, col=1)
fig.add_trace(go.Scatter(x=aapl.index,
y=aapl['SMA50'],
mode='lines',
line=dict(color='orange'),
name='SMA50'),
row=1, col=1)
fig.add_trace(go.Scatter(x=aapl.index,
y=aapl['SMA100'],
mode='lines',
line=dict(color='purple'),
name='SMA100'),
row=1, col=1)
fig.add_trace(go.Scatter(x=aapl.index,
y=aapl['SMA200'],
mode='lines',
line=dict(color='red'),
name='SMA200'),
row=1, col=1)
# Bollinger Bands
fig.add_trace(go.Scatter(x=aapl.index,
y=aapl['BB_UPPER'],
mode='lines',
line=dict(color='#00BFFF'),
name='Upper Band'),
row=1, col=1)
fig.add_trace(go.Scatter(x=aapl.index,
y=aapl['BB_LOWER'],
mode='lines',
line=dict(color='#00BFFF'),
name='Lower Band'),
row=1, col=1)
fig.add_annotation(text='Apple (AAPL)',
font=dict(color='white', size=40),
xref='paper', yref='paper',
x=0.5, y=0.65,
showarrow=False,
opacity=0.2)
# Volume
fig.add_trace(go.Bar(x=aapl.index,
y=aapl['Volume'],
name='Volume',
marker=dict(color='orange', opacity=1.0)),
row=4, col=1)
# Layout
fig.update_layout(title='AAPL Candlestick Chart From July 1st, 2010 to February 10t
yaxis=dict(title='Price (USD)'),
height=1000,
template = 'plotly_dark')
fig.show()
AAPL Candlestick Chart From July 1st, 2010 to February 10t
180
160
140
120
Price (USD)
100
80
60
Appl
40
Finally, the Candlestick chart above is a full representation of the price
and volume over time, along with additional indicators such as moving
averages, Bollinger Bands, Relative Strength Index, and ATR.
You've seen that, with Python, it is extremely easy not only to calculate,
but also to visualize a wide variety of technical indicators to analyze
stocks, as well as other assets.
Debt-to-Equity Ratio
Important Note: As of March 2023 it seems that yfinance, as well as other libraries, are
having the Exception: yfinance failed to decrypt Yahoo data response issue. I've tried
other methods to obtain these indicators, but none of them worked, or the data
obtained didn't seem to be reliable at all. I'll manually add the indicators and print them
below, however, I'll leave a code cell demonstrating how you would usually execute this
extraction using yfinance, so you can reproduce it when this issue gets solved.
aapl_eps = 5.89
aapl_pe_ratio = 26.12
aapl_roe = 147.94
aapl_dy = 0.60
# Printing data
print('\n')
print('Apple (AAPL) Fundamental Indicators: ')
print('\n')
print('Earnings per Share (EPS): ',aapl_eps)
print('Price-to-Earnings Ratio (P/E): ', aapl_pe_ratio)
print('Return on Equity (ROE): ', aapl_roe,"%")
print('Dividend Yield: ', aapl_dy,"%")
print('\n')
print('AMD Fundamental Indicators: ')
print('\n')
print('Earnings per Share (EPS): ',amd_eps)
print('Price-to-Earnings Ratio (P/E): ', amd_pe_ratio)
print('Return on Equity (ROE): ',amd_roe,"%")
print('Dividend Yield: ', amd_dy,"%")
print('\n')
Overall, it can be inferred that the indicators above suggest that AAPL is
a more profitable company with a more mature business model than
AMD.
4 | Backtesting
Below, we're going to backtest the RSI strategy and the moving average
crossover strategy, which are two different types of trading strategies,
on the EUR/USD currency pair, one of the most traded pair in Forex, in
three different timeframes.
RSI Backtesting
Consider that this backtesting is very simple, just to give you a direction
to look for when you perform your own backtests. Usually, it is also
important to consider the taxes, slippage, and other costs you'll have
while trading. Here, we're roughly talking about gross profits and
losses, so keep this in mind.
Hourly Data
[*********************100%***********************] 1 of 1 completed
Out[63]: Open High Low Close Adj Close Volume
Datetime
Out[64]: Adj
Open High Low Close Volume rsi
Close
Datetime
2021-03-22
1.194458 1.194600 1.193887 1.194030 1.194030 0 NaN
19:00:00+00:00
2021-03-22
1.194030 1.194458 1.193602 1.193602 1.193602 0 NaN
20:00:00+00:00
2021-03-22
1.193602 1.194315 1.193460 1.193887 1.193887 0 NaN
21:00:00+00:00
2021-03-22
1.193745 1.194172 1.193460 1.193745 1.193745 0 NaN
22:00:00+00:00
2021-03-22
1.193745 1.194172 1.193317 1.193602 1.193602 0 NaN
23:00:00+00:00
2023-03-21
1.077935 1.078516 1.076890 1.077470 1.077470 0 71.456319
15:00:00+00:00
2023-03-21
1.077354 1.077935 1.076542 1.077122 1.077122 0 68.781848
16:00:00+00:00
2023-03-21
1.077238 1.077470 1.076426 1.076658 1.076658 0 65.274986
17:00:00+00:00
2023-03-21
1.076658 1.077006 1.076079 1.077006 1.077006 0 66.648418
18:00:00+00:00
2023-03-21
1.076774 1.077354 1.076542 1.076890 1.076890 0 65.714443
19:00:00+00:00
# Adding annotation
fig.add_annotation(text='EUR/USD 1HR',
font=dict(color='white', size=40),
xref='paper', yref='paper',
x=0.5, y=0.65,
showarrow=False,
opacity=0.2)
# Layout
fig.update_layout(title='EUR/USD Hourly Candlestick Chart from 2021 to 2023',
yaxis=dict(title='Price'),
height=1000,
template = 'plotly_dark')
# Configuring subplots
fig.update_xaxes(rangeslider_visible=False)
fig.update_yaxes(title_text='Price', row = 1)
fig.update_yaxes(title_text='RSI', row = 2)
fig.show()
EUR/USD Hourly Candlestick Chart from 2021 to 2023
1.2
1.15
1.1
Price
EUR/
In [66]: # Defining the parameters for the RSI strategy
rsi_period = 14
overbought = 70
oversold = 30
print('\n')
print(f"Number of trades: {num_trades}")
print(f"Initial capital: ${initial_capital}")
print(f"Final capital: ${final_capital:.2f}")
print(f"Total return: {total_return:.2f}%")
print('\n')
fig.add_trace(go.Scatter(x=hourly_eur_usd.index,
y=hourly_eur_usd['portfolio_value'].round(2),
mode='lines',
line=dict(color='#00BFFF'),
name='Portfolio Value'))
fig.show()
Number of trades: 359
Initial capital: $100
Final capital: $100.70
Total return: 0.70%
102
101.5
101
Daily Data
In [67]: # Loading daily EUR/USD pair data for the last eight years
daily_eur_usd = yf.download('EURUSD=X', start='2015-03-13', end='2023-03-13', inter
[*********************100%***********************] 1 of 1 completed
Date
Date
# Annotation
fig.add_annotation(text='EUR/USD 1D',
font=dict(color='white', size=40),
xref='paper', yref='paper',
x=0.5, y=0.65,
showarrow=False,
opacity=0.2)
# Configuring subplots
fig.update_xaxes(rangeslider_visible=False)
fig.update_yaxes(title_text='Price', row = 1)
fig.update_yaxes(title_text='RSI', row = 2)
fig.show()
EUR/USD Daily Candlestick Chart from 2015 to 2023
1.25
1.2
1.15
Price
1.1
EUR
In [71]: # Defining the parameters for the RSI strategy
rsi_period = 14
overbought = 70
oversold = 30
print('\n')
print(f"Number of trades: {num_trades}")
print(f"Initial capital: ${initial_capital}")
print(f"Final capital: ${final_capital:.2f}")
print(f"Total return: {total_return:.2f}%")
print('\n')
fig.add_trace(go.Scatter(x=daily_eur_usd.index,
y=daily_eur_usd['portfolio_value'].round(2),
mode='lines',
line=dict(color='#00BFFF'),
name='Portfolio Value'))
fig.show()
Number of trades: 55
Initial capital: $100
Final capital: $97.99
Total return: -2.01%
104
103
102
Weekly Data
[*********************100%***********************] 1 of 1 completed
Date
# Annotations
fig.add_annotation(text='EUR/USD 1W',
font=dict(color='white', size=40),
xref='paper', yref='paper',
x=0.5, y=0.65,
showarrow=False,
opacity=0.2)
# Configuring subplots
fig.update_xaxes(rangeslider_visible=False)
fig.update_yaxes(title_text='Price', row = 1)
fig.update_yaxes(title_text='RSI', row = 2)
fig.show()
EUR/USD Weekly Candlestick Chart from 2015 to 2023
1.25
1.2
1.15
Price
1.1
EUR
In [75]: # Defining the parameters for the RSI strategy
rsi_period = 14
overbought = 70
oversold = 30
print('\n')
print(f"Number of trades: {num_trades}")
print(f"Initial capital: ${initial_capital}")
print(f"Final capital: ${final_capital:.2f}")
print(f"Total return: {total_return:.2f}%")
print('\n')
fig.add_trace(go.Scatter(x=weekly_eur_usd.index,
y=weekly_eur_usd['portfolio_value'].round(2),
mode='lines',
line=dict(color='#00BFFF'),
name='Portfolio Value'))
fig.show()
Number of trades: 8
Initial capital: $100
Final capital: $100.32
Total return: 0.32%
100.2
100
Several conclusions can be drawn from the results above. First, the
hourly timeframe is characterized by high volatility, leading to frequent
swings in the RSI between 30 and 70, and resulting in a significantly
higher number of trades compared to the daily and weekly timeframes.
Overall, the backtesting results indicate that the strategy was not very
successful, with the portfolio losing 2.01% of its initial value on the daily
timeframe and generating only small returns of 0.32% and 0.31% on the
weekly and hourly timeframes, respectively. It's important to note,
however, that this backtesting did not account for slippage and other
additional costs, which could have a significant impact on the overall
profitability of the strategy. Therefore, it's important to carefully
consider these factors when applying this kind of strategy in real-world
scenarios.
When the shorter moving average crosses above the longer moving
average, it's a signal to buy, and when it crosses below the longer
moving average, it's a signal to sell.
Once again, we'll consider an initial capital of 100.00 dollars for this
backtesting.
Hourly Data
In [76]: # Creating the 9-period exponential moving average with the TA library
hourly_eur_usd['ema9'] = ta.trend.ema_indicator(hourly_eur_usd['Adj Close'], window
Datetime
# 9 EMA
fig.add_trace(go.Scatter(x=hourly_eur_usd.index,
y=hourly_eur_usd['ema9'],
mode='lines',
line=dict(color='yellow'),
name='EMA 9'))
# 20 SMA
fig.add_trace(go.Scatter(x=hourly_eur_usd.index,
y=hourly_eur_usd['sma20'],
mode='lines',
line=dict(color='green'),
name='SMA 20'))
# Annotation
fig.add_annotation(text='EUR/USD 1HR',
font=dict(color='white', size=40),
xref='paper', yref='paper',
x=0.5, y=0.65,
showarrow=False,
opacity=0.2)
# Layout
fig.update_layout(title='EUR/USD Hourly Candlestick Chart from 2021 to 2023',
yaxis=dict(title='Price'),
height=1000,
template = 'plotly_dark')
fig.update_xaxes(rangeslider_visible=False)
fig.show()
EUR/USD Hourly Candlestick Chart from 2021 to 2023
1.2
1.15
EUR/
In [78]: # Defining the parameters for the moving average crossover strategy
short_ma = 'ema9'
long_ma = 'sma20'
print('\n')
print(f"Number of trades: {num_trades}")
print(f"Initial capital: ${initial_capital}")
print(f"Final capital: ${final_capital:.2f}")
print(f"Total return: {total_return:.2f}%")
print('\n')
fig.add_trace(go.Scatter(x=hourly_eur_usd.index,
y=hourly_eur_usd['portfolio_value'].round(2),
mode='lines',
line=dict(color='#00BFFF'),
name='Portfolio Value'))
fig.show()
Number of trades: 777
Initial capital: $100
Final capital: $101.48
Total return: 1.48%
101
100
Daily Data
Date
fig.add_trace(go.Scatter(x=daily_eur_usd.index,
y=daily_eur_usd['ema9'],
mode='lines',
line=dict(color='yellow'),
name='EMA 9'))
fig.add_trace(go.Scatter(x=daily_eur_usd.index,
y=daily_eur_usd['sma20'],
mode='lines',
line=dict(color='green'),
name='SMA 20'))
fig.add_annotation(text='EUR/USD 1D',
font=dict(color='white', size=40),
xref='paper', yref='paper',
x=0.5, y=0.65,
showarrow=False,
opacity=0.2)
fig.update_xaxes(rangeslider_visible=False)
fig.show()
EUR/USD Daily Candlestick Chart from 2015 to 2023
1.25
1.2
1.15 EUR
In [81]: # Defining the parameters for the moving average crossover strategy
short_ma = 'ema9'
long_ma = 'sma20'
print('\n')
print(f"Number of trades: {num_trades}")
print(f"Initial capital: ${initial_capital}")
print(f"Final capital: ${final_capital:.2f}")
print(f"Total return: {total_return:.2f}%")
print('\n')
fig.add_trace(go.Scatter(x=daily_eur_usd.index,
y=daily_eur_usd['portfolio_value'].round(2),
mode='lines',
line=dict(color='#00BFFF'),
name='Portfolio Value'))
fig.show()
Number of trades: 131
Initial capital: $100
Final capital: $93.95
Total return: -6.05%
101
100
99
98
Weekly Data
Date
fig.add_trace(go.Scatter(x=weekly_eur_usd.index,
y=weekly_eur_usd['ema9'],
mode='lines',
line=dict(color='yellow'),
name='EMA 9'))
fig.add_trace(go.Scatter(x=weekly_eur_usd.index,
y=weekly_eur_usd['sma20'],
mode='lines',
line=dict(color='green'),
name='SMA 20'))
fig.add_annotation(text='EUR/USD 1WK',
font=dict(color='white', size=40),
xref='paper', yref='paper',
x=0.5, y=0.65,
showarrow=False,
opacity=0.2)
fig.update_xaxes(rangeslider_visible=False)
fig.show()
EUR/USD Weekly Candlestick Chart from 2015 to 2023
1.25
1.2
1.15 EUR/
In [84]: # Defining the parameters for the moving average crossover strategy
short_ma = 'ema9'
long_ma = 'sma20'
print('\n')
print(f"Number of trades: {num_trades}")
print(f"Initial capital: ${initial_capital}")
print(f"Final capital: ${final_capital:.2f}")
print(f"Total return: {total_return:.2f}%")
print('\n')
fig.add_trace(go.Scatter(x=weekly_eur_usd.index,
y=weekly_eur_usd['portfolio_value'].round(2),
mode='lines',
line=dict(color='#00BFFF'),
name='Portfolio Value'))
fig.show()
Number of trades: 24
Initial capital: $100
Final capital: $93.20
Total return: -6.80%
100
99
98
Although the plots of the total portfolio values for the RSI strategies
appeared to be stationary, the portfolio evolution for the moving
average crossover strategy exhibits some sort of trend, especially in the
hourly data. As can be seen, the portfolio experienced gradual losses
from April 2021 to October 2021, which then turned to exponential
losses. However, since May 2022, the strategy has been generating
gains again and has been performing quite well in recent months, at
least on the 1 hour timeframe.
Despite this, the strategy generated only a 1.37% return after 777 trades
on the hourly chart, without accounting for taxes, slippage, and other
costs. On the daily timeframe, the strategy resulted in 131 trades with a
total loss of 6.05%, closing at 93.95 dollars. Similarly, on the weekly
timeframe, the strategy presents a total loss of 6.80%. And, of course, it
is important to mention that additional costs and factors like slippage
could further impact the final returns.
In this notebook, we have only tested the RSI and the moving average
crossover strategies individually. You can, however, combine these
strategies, or use the RSI in conjunction with other indicators such as
Bollinger Bands, to develop more complex strategies.
Conclusion
This notebook provided an introduction to using Python and Data
Science for Financial Markets. You learned how to analyze financial
assets, build portfolios, and optimize them for the best risk-adjusted
returns. You also learned how to evaluate portfolios and strategies,
calculate technical indicators, and plot them on a candlestick chart with
Plotly. Furthermore, you learned how to obtain some of the most widely
used fundamental indicators and backtest technical strategies on
historic data.
However, this is just the beginning of your journey into the world of
financial data analysis. To avoid overwhelming you with information,
I've decided to conclude this introductory notebook here.
But don't worry! Soon, I'll post the second part of this notebook, which
will be entirely dedicated to using Machine Learning algorithms for
financial markets. So, stay tuned for that!
Best regards,