avatarAlexzap

Summary

The provided website content offers a comprehensive technical analysis of top growth stocks, with a focus on NVIDIA (NVDA), utilizing various algorithmic trading strategies and risk assessment methodologies to evaluate its investment potential.

Abstract

The website delves into the application of advanced technical analysis techniques, including backtesting volatility resilience versus profitability, to assess the performance of top growth stocks like NVIDIA. It employs Python for algorithmic trading (algo-trading), analyzing historical price and volume data to identify patterns and trends using technical indicators such as moving averages, oscillators, and chart patterns. The objectives include loading historical stock data, using risk-aware technical analysis, joint analysis of several technical indicators, and backtesting basic and mixed algo-trading strategies. The content covers the Sharpe Ratio of top tech stocks, NVDA's Probabilistic Sharpe Ratio (PSR) with 95% Confidence Intervals (CI), NVDA's Keltner Channel (KC) strategy, VWAP support and resistance, fundamental analysis, backtesting NVDA's Williams %R & MACD trading strategy, and Monte Carlo simulation of NVDA's Value at Risk (VaR). The analysis aims to provide insights into the stock's performance and risk profile, guiding investors in making informed trading decisions.

Opinions

  • The content suggests that technical analysis is a skill that improves with study and experience, emphasizing the importance of continuous learning in the field of algorithmic trading.
  • The author appears to advocate for the use of the Sharpe Ratio as a valuable tool for assessing the risk-adjusted performance of investments, with a particular focus on NVIDIA's strong performance in this regard.
  • The website content implies that the Keltner Channel (KC) indicator is effective in providing breakout signals and that the KC strategy has outperformed the SPY ETF, indicating NVIDIA's potential for high returns.
  • The VWAP is presented as a useful indicator for determining market trends and identifying support and resistance levels in NVIDIA's stock price.
  • Fundamental analysis of NVIDIA, including metrics such as beta, PEG ratio, and debt-to-equity ratio, is used to support the argument that NVIDIA is a less risky investment compared to its peers.
  • The hybrid trading strategy combining Williams %R and MACD indicators is proposed as a method to maximize expected returns by avoiding false trading signals, with the content asserting its superiority over industry averages.
  • The use of Monte Carlo simulations to calculate Value at Risk (VaR) is recommended as a means to quantify potential losses and manage investment risk for NVIDIA's stock.

Full-Fledged Technical Analysis of Top Growth Stocks: Backtesting Volatility Resilience vs Profitability

Backtesting a wide spectrum of algo trading strategies, addressing volatility resilience vs profitability, simulating VaR, and computing trend-following technical indicators of top growth stocks.

“Technical analysis is a skill that improves with experience and study. Always be a student, and keep learning.” — John J Murphy

Graphic Design Template via Canva.
  • This article focuses on introducing some of the simple yet powerful techniques applied during technical analysis with Python for algorithmic trading (aka algo trading) of top growth stocks to watch.
  • Technical Analysis is the basis for algo trading when you use software products to open and close trades according to set rules such as points of price movement in an underlying market. Once the current market conditions match any predetermined criteria, trading algorithms (algos) can execute a buy or sell order on your behalf.
  • Technical analysis based algo trading analyzes historical price and volume data to identify patterns and trends. By using a combination of chart patterns, moving averages, oscillators, and other technical indicators, traders aim to forecast future price movements and make trading decisions.

Objectives

  • Load stock historical stock data from the web.
  • Use risk-aware technical analysis for algo-trading.
  • Joint analysis of several technical indicators.
  • Backtesting basic and mixed algo-trading strategies.

Contents

  • The Sharpe Ratio of Top 7 Tech Stocks
  • NVDA PSR & 95% CI
  • NVDA Daily/Cumulative Returns
  • NVDA Keltner Channel (KC) Strategy
  • NVDA VWAP Support & Resistance
  • NVDA Fundamental Analysis
  • Backtesting NVDA Williams %R & MACD Trading Strategy
  • Monte Carlo Simulation of NVDA Value-at-Risk (VaR)
  • NVDA RSI & Bollinger Bands Support-Resistance
  • Risk-Return Comparison of 10 Stocks to Watch
  • Backtest GOOG RSI-SMA Trading Strategies
  • AMD SMA-MACD-ADX Trend-Following Analysis

Let’s delve into the specifics of the proposed algorithmic trading methodology in Python.

The Sharpe Ratio of Top 7 Tech Stocks

  • Let’s look at the Sharpe Ratio (SR) [1] of top 7 tech stocks such as MSFT, AAPL, NVDA, META, GOOG, AMZN, and TSLA. Here, S&P 500 will be used as a benchmark rather than the risk-free interest rate.
  • Typically, a SR of 1 or better is good, 2 or better is very good, and 3 or better is excellent [2].
  • Importing libraries and loading the input stock and S&P 500 data in bulk
# Importing libraries
import yfinance as yf
import pandas as pd
import numpy as np
import plotly.io as pio
import plotly.express as px

# Set plot style
pio.templates.default = "seaborn"

# Loading stock data in bulk; keeping only dates with values across all ticker symbols
stock_data = yf.download('MSFT AAPL NVDA META GOOG AMZN TSLA', period='5Y').dropna()
benchmark_data = data = yf.download('^GSPC', period='5Y').dropna()

stock_data = stock_data['Close']
stock_data.tail()

icker       AAPL      AMZN        GOOG       META      MSFT        NVDA       TSLA
Date       
2024-08-19 225.889999 178.220001 168.399994 529.280029 421.529999 130.000000 222.720001
2024-08-20 226.509995 178.880005 168.960007 526.729980 424.799988 127.250000 221.100006
2024-08-21 226.399994 180.110001 167.630005 535.159973 424.140015 128.500000 223.270004
2024-08-22 224.529999 176.130005 165.490005 531.929993 415.549988 123.739998 210.660004
2024-08-23 226.149994 176.964996 166.759995 531.090027 415.239990 128.009995 217.500000

benchmark_data = benchmark_data[['Close']].rename(columns={'Close':'S&P 500'})
benchmark_data.tail()

           S&P 500
Date 
2024-08-19 5608.250000
2024-08-20 5597.120117
2024-08-21 5620.850098
2024-08-22 5570.640137
2024-08-23 5618.700195
  • Plotting the stock and S&P 500 Close price data
# Visualization of stock data
fig = px.line(stock_data, 
              title='Stock Data',
              labels={'value':'$ USD',
                      'variable':'Ticker'})
fig.show()

# Visualization of benchmark data
fig = px.line(benchmark_data,
              title='Benchmark Data - S&P 500',
              labels={'value':'$ USD'})
fig.update_layout(showlegend=False)
fig.show()
Stock Close price
S&P 500 Close price
  • Calculating and plotting the stock and benchmark daily returns
stock_returns = stock_data.pct_change()

fig = px.line(stock_returns,
              title='Stock % Change',
              labels={'variable':'Ticker'})
fig.show()
Stock daily returns
sp_returns = benchmark_data['S&P 500'].pct_change()

fig = px.line(sp_returns, title='S&P 500 % Change')
fig.update_layout(showlegend=False)
fig.show()
S&P 500 daily returns
  • Calculating and plotting the difference between stock and benchmark daily returns
excess_returns = stock_returns.sub(sp_returns, axis=0).dropna()

fig = px.line(excess_returns, labels={'variable':'Ticker'}, title='Excess Returns')
fig.show()
Stock Excess Returns
  • Calculating the average of the above Excess Returns to plot the stock yields per day compared to S&P 500
avg_excess_return = excess_returns.mean()

fig = px.bar(avg_excess_return, color=avg_excess_return.index)
fig.update_layout(showlegend=False, title='Average Excess Returns')
fig.show()
Average Excess Returns
  • Calculating STD of the Excess Returns to assess the amount of stock investment risk compared to S&P 500
sd_excess_return = excess_returns.std()

fig = px.bar(sd_excess_return, color=sd_excess_return.index)
fig.update_layout(showlegend=False, title='Standard Deviation of the Return Difference')
fig.show()
STD of the Excess Returns
  • Computing and plotting the annualized SR as the Average Excess Returns to STD of the Excess Returns ratio [1, 2]
daily_sharpe_ratio = avg_excess_return.div(sd_excess_return)

# annualized Sharpe ratio for trading days in a year (5 days, 52 weeks, no holidays)
annual_factor = np.sqrt(252)
annual_sharpe_ratio = daily_sharpe_ratio.mul(annual_factor)

fig = px.bar(annual_sharpe_ratio, color=annual_sharpe_ratio.index)
fig.update_layout(showlegend=False, title='Annualized Sharpe Ratio: Stocks vs S&P 500')
fig.show()
Annualized Sharpe Ratio: Stocks vs S&P 500

Inferences:

  • AMZN, GOOG, META, and MSFT have SR<1.0. A ratio under 1.0 is considered sub-optimal.
  • AAPL and TSLA have the SR values of 0.97 and 1.04, respectively. A SR of close to 1.0 or greater is typically considered good.
  • NVDA has the SR of 1.63 that is higher than the other 6 tech stocks.
  • In risk-adjusted terms, NVDA would be the most acceptable investment opportunity.

NVDA PSR & 95% CI

# IMPORTING PACKAGES

import requests
import numpy as np
import matplotlib.pyplot as plt
import pandas as pd
from termcolor import colored as cl
from math import floor

plt.rcParams['figure.figsize'] = (12,6)
plt.style.use('fivethirtyeight')

# EXTRACTING STOCK DATA

def get_historical_data(symbol, start_date):
    api_key = 'YOUR API KEY'
    api_url = f'https://api.twelvedata.com/time_series?symbol={symbol}&interval=1day&outputsize=5000&apikey={api_key}'
    raw_df = requests.get(api_url).json()
    df = pd.DataFrame(raw_df['values']).iloc[::-1].set_index('datetime').astype(float)
    df = df[df.index >= start_date]
    df.index = pd.to_datetime(df.index)
    return df

intc = get_historical_data('NVDA', '2022-01-01')
intc.tail()

           open      high      low     close  volume
datetime     
2024-08-19 124.28000 130.00000 123.42 130.00 318333600.0
2024-08-20 128.39999 129.88000 125.89 127.25 300087400.0
2024-08-21 127.32000 129.35001 126.66 128.50 257883600.0
2024-08-22 130.02000 130.75000 123.10 123.74 376189100.0
2024-08-23 125.86000 129.60001 125.22 129.37 322154200.0

intc.shape
(664, 5)
  • Plotting the NVDA Plotly candlesticks
import plotly.graph_objects as go

import pandas as pd
from datetime import datetime

df = intc.copy()

fig = go.Figure(data=[go.Candlestick(x=df.index,
                open=df['open'],
                high=df['high'],
                low=df['low'],
                close=df['close'])])

fig.show()
NVDA candlesticks
  • Candlesticks are useful when trading as they show 4 price points (open, close, high, and low) throughout the period of interest. If the real body is green, it means the close was higher than the open. When the real body is red, it means the close was lower than the open. Emotion often dictates trading, which can be read in candlestick charts.
  • Calculating the mean, skewness, and kurtosis of daily returns
prices=intc['close']
daily_return = prices.pct_change(1)
# Check the mean, skewness and kurtosis
print('- NVDA daily returns:')
print('Mean:', daily_return.mean()) 
print('Skewness:', daily_return.skew())
print('Kurtosis:', daily_return.kurtosis()) 

- NVDA daily returns:
Mean: 0.002827624633924218
Skewness: 0.7275340047117553
Kurtosis: 3.969024954729987
  • Calculating the NVDA Probabilistic Sharpe Ratio (PSR) with 95% Confidence Intervals (CI) [4, 5]
skew0=0.7275340047117553
kurt0=3.969024954729987

from scipy.stats import norm

# Estimated Sharpe Ratio (SR) and number of observations (N)
SR_hat = 1.63
N = 252

# Calculate Standard Deviation of Sharpe Ratio (Std Dev SR)
def standard_deviation_sharpe_ratio(sharpe_ratio, num_obs, skewness=0, kurtosis=3):
    """Estimates standard Deviation of Sharpe Ratio
    
    Parameters:
    - sharpe_ratio: Sharpe ratio of the strategy
    - bench_sharpe_ratio: Sharpe ratio of the benchmark
    - num_obs: Number of observations
    - skewness: Skewness of the strategy returns (default 0)
    - kurtosis: Kurtosis of the strategy returns (default 3)
    
    Returns:
    - std_dev: Standard Deviation of Sharpe Ratio
    """
    return np.sqrt(
        (1 - skewness*sharpe_ratio + 
         (kurtosis-1)/4*sharpe_ratio**2
        ) / (num_obs-1)
    )

# Portfolio Returns with Normality
sigma_hat_normal_returns = standard_deviation_sharpe_ratio(sharpe_ratio=SR_hat, 
                                                           num_obs=N, 
                                                           skewness=skew0, 
                                                           kurtosis=kurt0)

# Portfolio Returns with Non-Normality
sigma_hat_higher_moments = standard_deviation_sharpe_ratio(sharpe_ratio=SR_hat, 
                                                           num_obs=N, 
                                                           skewness=skew0, 
                                                           kurtosis=kurt0)

def two_sided_confidence_intervals(sharpe_ratio, standard_deviation, confidence_level):
    """Sharpe Ratio two-sided confidence intervals

    Parameters:
    - sharpe_ratio: Sharpe ratio of the strategy
    - standard_devation: Standard Deviation of Sharpe Ratio
    - confidence_level: level of confidence (fraction: i.e. 0.90 for 90%)

    Returns:
    - lower_bound: two-sided lower bound
    - upper_bound: two-sided upper bound
    """
    # Two-sided (1-alpha)% Confidence Interval
    alpha = 1 - confidence_level
    Z_alpha_over_2 = norm.ppf(1-alpha/2)  # 95% CI

    lower_bound = sharpe_ratio - Z_alpha_over_2 * standard_deviation
    upper_bound= sharpe_ratio + Z_alpha_over_2 * standard_deviation
    
    return lower_bound, upper_bound

lower_norm, upper_norm = two_sided_confidence_intervals(SR_hat, 
                                                  sigma_hat_normal_returns,
                                                  0.95)

lower_non_norm, upper_non_norm = two_sided_confidence_intervals(SR_hat, 
                                                  sigma_hat_higher_moments,
                                                  0.95)

# Print the confidence intervals
print(f"Normally Distributed; Two-sided 95% CI:     [{lower_norm:2.4f}, {upper_norm:2.4f}]")
print(f"Non-Normally Distributed; Two-sided 95% CI: [{lower_non_norm:2.4f}, {upper_non_norm:2.4f}]")

Normally Distributed; Two-sided 95% CI:     [1.4647, 1.7953]
Non-Normally Distributed; Two-sided 95% CI: [1.4647, 1.7953]

def probabilistic_sharpe_ratio(sharpe_ratio, bench_sharpe_ratio, num_obs, skewness, kurtosis):
    """
    Calculates the Probabilistic Sharpe Ratio
    
    Parameters:
    - sharpe_ratio: Sharpe ratio of the strategy
    - bench_sharpe_ratio: Sharpe ratio of the benchmark
    - num_obs: Number of observations
    - skewness: Skewness of the strategy returns
    - kurtosis: Kurtosis of the strategy returns
    
    Returns:
    - psr: Probabilistic Sharpe Ratio
    """
    
    sr_diff = sharpe_ratio - bench_sharpe_ratio
    sr_vol = standard_deviation_sharpe_ratio(sharpe_ratio, num_obs, skewness, kurtosis)
    psr = norm.cdf(sr_diff / sr_vol)
    
    return psr

SR = 1.63  # Your strategy's Sharpe Ratio
Bench_SR = 0.65  # Benchmark Sharpe Ratio
#https://www.morningstar.com/indexes/spi/spx/risk
skewness = 0.72  # Skewness of your strategy
kurtosis = 3.97  # Kurtosis of your strategy
N = 252  # Number of observations (trading days in a year, for example)

#Skewness: 0.7275340047117553
#Kurtosis: 3.969024954729987

# Calculate PSR
PSR_value = probabilistic_sharpe_ratio(SR, Bench_SR, N, skewness, kurtosis)
print(f"Probabilistic Sharpe Ratio: {PSR_value}")

Probabilistic Sharpe Ratio: 1.0

Inferences:

  • Combinations of skewness and kurtosis in returns distribution impact the STD and the confidence bands of the SR.
  • The PSR metric accounts for the STD and uncertainty associated with estimating SR (Type I errors) [5].

NVDA Daily/Cumulative Returns

  • Let’s continue using the NVDA stock dataset 2022–2024 intc, being referred to as DAT1.
  • Plotting the DAT1 candlesticks and volume with mplfinance-module
!pip install mplfinance
import mplfinance as mpf
import pandas as pd
import numpy as np
import matplotlib.pyplot as plt

# Create a style with adjusted font size
style = mpf.make_mpf_style(base_mpl_style='default', rc={'font.size': 12})  # Set the font size here
# Create a new figure and set the title
fig, axes = mpf.plot(intc, type='candle', volume=True,
   title='NVDA Stock Price', ylabel='Price', ylabel_lower='Volume',
   show_nontrading=True, returnfig=True, style=style)
plt.style.use('dark_background')
# Retrieve the axis objects from the returned list
ax = axes[0]

# Customize the appearance of the plot
ax.grid(True)  # Display grid lines

# Save the plot to an image file
#fig.savefig('tesla_candlestick_chart.png')

# Show the plot on the screen
mpf.show()
NVDA Candlesticks vs Volume
  • We can see that the lower the trading volume, the skinnier the candlestick body. A higher-volume days result in wider candlestick. Here, we plot volume at the bottom of a price chart as a series of rectangles.
  • Computing and plotting the stock daily returns
data=intc.copy()

# Convert 'volume' column to numeric, handle non-numeric values
data['volume'] = pd.to_numeric(data['volume'], errors='coerce')
non_numeric_values = data['volume'].isnull()
if non_numeric_values.any():
   data['volume'] = np.where(non_numeric_values, 0, data['volume'])

prices=intc['close']
daily_return = prices.pct_change(1)
daily_return.plot(title='NVDA Daily Returns',figsize=(12, 6))

# Check the mean, skewness and kurtosis
print('- NVDA daily returns:')
print('Mean:', daily_return.mean()) 
print('Skewness:', daily_return.skew())
print('Kurtosis:', daily_return.kurtosis()) 

- NVDA daily returns:
Mean: 0.002827624633924218
Skewness: 0.7275340047117553
Kurtosis: 3.969024954729987
NVDA Daily Returns
  • Daily returns provide valuable insights into short-term performance, allow for meaningful comparisons, and help identify market trends.
  • By analyzing the above daily returns, investors can gain insights into the overall direction of the market. For example, a series of consistently positive daily returns in the technology sector may indicate a bullish market sentiment for tech stocks.
  • Computing and plotting the stock cumulative returns
cum_return = (1 + daily_return).cumprod() - 1 
cum_return.plot(title='NVDA Cumulative Returns',figsize=(12, 6))
NVDA Cumulative Returns
  • Cumulative returns are the total gains or losses of an investment over a specific period. It is a crucial metric that investors use to determine the overall performance of their portfolio.
  • This information can help investors adjust their portfolio to maximize their returns.
  • However, taxes can substantially reduce the cumulative returns for most investments unless they are held in tax-advantaged accounts.

NVDA Keltner Channel (KC) Strategy

  • In this section, the goal is to backtest the Keltner Channel (KC) indicator [3] that has 3 channel lines: middle line, upper line and lower line. The lines are calculated as follows [6]:

Upper line = EMA + (ATR x multiplier) Middle line = EMA Lower line = EMA — (ATR x multiplier)

  • The slope of the channel denotes the price trend in the market. A rising channel implies that an uptrend is in place; a falling channel indicates a downtrend; whereas a flat or sideways channel implies a ranging market.
  • Calculating the KC indicator for the DAT1 dataset [3]
# KELTNER CHANNEL CALCULATION

def get_kc(high, low, close, kc_lookback, multiplier, atr_lookback):
    tr1 = pd.DataFrame(high - low)
    tr2 = pd.DataFrame(abs(high - close.shift()))
    tr3 = pd.DataFrame(abs(low - close.shift()))
    frames = [tr1, tr2, tr3]
    tr = pd.concat(frames, axis = 1, join = 'inner').max(axis = 1)
    atr = tr.ewm(alpha = 1/atr_lookback).mean()
    
    kc_middle = close.ewm(kc_lookback).mean()
    kc_upper = close.ewm(kc_lookback).mean() + multiplier * atr
    kc_lower = close.ewm(kc_lookback).mean() - multiplier * atr
    
    return kc_middle, kc_upper, kc_lower
# kc_lookback, multiplier, atr_lookback  20, 2, 10  
intc = intc.iloc[:,:4]
intc['kc_middle'], intc['kc_upper'], intc['kc_lower'] = get_kc(intc['high'], intc['low'], intc['close'], 20, 2, 20)
intc.tail()

           open      high      low    close  kc_middle  kc_upper   kc_lower
datetime       
2024-08-19 124.28000 130.00000 123.42 130.00 115.434182 128.917605 101.950759
2024-08-20 128.39999 129.88000 125.89 127.25 115.996840 129.217092 102.776588
2024-08-21 127.32000 129.35001 126.66 128.50 116.592229 129.420469 103.763988
2024-08-22 130.02000 130.75000 123.10 123.74 116.932599 129.884427 103.980771
2024-08-23 125.86000 129.60001 125.22 129.37 117.524856 130.415094 104.634618
  • Implementing the KC trading strategy [3]
# KELTNER CHANNEL STRATEGY

def implement_kc_strategy(prices, kc_upper, kc_lower):
    buy_price = []
    sell_price = []
    kc_signal = []
    signal = 0
    
    for i in range(len(prices)):
        if prices.iloc[i] < kc_lower.iloc[i] and prices.iloc[i+1] > prices.iloc[i]:
            if signal != 1:
                buy_price.append(prices.iloc[i])
                sell_price.append(np.nan)
                signal = 1
                kc_signal.append(signal)
            else:
                buy_price.append(np.nan)
                sell_price.append(np.nan)
                kc_signal.append(0)
        elif prices.iloc[i] > kc_upper.iloc[i] and prices.iloc[i+1] < prices.iloc[i]:
            if signal != -1:
                buy_price.append(np.nan)
                sell_price.append(prices.iloc[i])
                signal = -1
                kc_signal.append(signal)
            else:
                buy_price.append(np.nan)
                sell_price.append(np.nan)
                kc_signal.append(0)
        else:
            buy_price.append(np.nan)
            sell_price.append(np.nan)
            kc_signal.append(0)
            
    return buy_price, sell_price, kc_signal

buy_price, sell_price, kc_signal = implement_kc_strategy(intc['close'], intc['kc_upper'], intc['kc_lower'])
  • Calculating and plotting the KC trading signals for the DAT1 dataset
# TRADING SIGNALS PLOT
plt.figure(figsize=(12,6))
plt.plot(intc['close'], linewidth = 2, label = 'NVDA')
plt.plot(intc['kc_upper'], linewidth = 2, color = 'orange', linestyle = '--', label = 'KC UPPER')
plt.plot(intc['kc_middle'], linewidth = 1.5, color = 'grey', label = 'KC MIDDLE')
plt.plot(intc['kc_lower'], linewidth = 2, color = 'orange', linestyle = '--', label = 'KC LOWER')
plt.plot(intc.index, buy_price, marker = '^', color = 'green', markersize = 15, linewidth = 0, label = 'BUY SIGNAL')
plt.plot(intc.index, sell_price, marker = 'v', color= 'r', markersize = 15, linewidth = 0, label = 'SELL SIGNAL')
plt.legend(loc = 'lower right')
plt.title('NVDA KELTNER CHANNEL 20/2/20 TRADING SIGNALS')
plt.show()
NVDA KELTNER CHANNEL 20/2/20 TRADING SIGNALS
  • Creating the stock position [3]
# STOCK POSITION

position = []
for i in range(len(kc_signal)):
    if kc_signal[i] > 1:
        position.append(0)
    else:
        position.append(1)
        
for i in range(len(intc['close'])):
    if kc_signal[i] == 1:
        position[i] = 1
    elif kc_signal[i] == -1:
        position[i] = 0
    else:
        position[i] = position[i-1]
        
close_price = intc['close']
kc_upper = intc['kc_upper']
kc_lower = intc['kc_lower']
kc_signal = pd.DataFrame(kc_signal).rename(columns = {0:'kc_signal'}).set_index(intc.index)
position = pd.DataFrame(position).rename(columns = {0:'kc_position'}).set_index(intc.index)

frames = [close_price, kc_upper, kc_lower, kc_signal, position]
strategy = pd.concat(frames, join = 'inner', axis = 1)

strategy

          close   kc_upper kc_lower kc_signal kc_position
datetime     
2022-01-03 30.121 31.973000 28.269000 0 1
2022-01-04 29.290 32.770956 26.619776 0 1
2022-01-05 27.604 32.281595 25.646342 0 1
2022-01-06 28.178 31.916415 25.589327 0 1
2022-01-07 27.247 31.489336 25.353895 0 1
... ... ... ... ... ...
2024-08-19 130.000 128.917605 101.950759 -1 0
2024-08-20 127.250 129.217092 102.776588 0 0
2024-08-21 128.500 129.420469 103.763988 0 0
2024-08-22 123.740 129.884427 103.980771 0 0
2024-08-23 129.370 130.415094 104.634618 0 0
664 rows × 5 columns
  • Backtesting the above KC strategy [3] by investing $100k in NVDA
# BACKTESTING

intc_ret = pd.DataFrame(np.diff(intc['close'])).rename(columns = {0:'returns'})
kc_strategy_ret = []

for i in range(len(intc_ret)):
    returns = intc_ret['returns'][i]*strategy['kc_position'][i]
    kc_strategy_ret.append(returns)
    
kc_strategy_ret_df = pd.DataFrame(kc_strategy_ret).rename(columns = {0:'kc_returns'})
investment_value = 100000
number_of_stocks = floor(investment_value/intc['close'][0])
kc_investment_ret = []

for i in range(len(kc_strategy_ret_df['kc_returns'])):
    returns = number_of_stocks*kc_strategy_ret_df['kc_returns'][i]
    kc_investment_ret.append(returns)

kc_investment_ret_df = pd.DataFrame(kc_investment_ret).rename(columns = {0:'investment_returns'})
total_investment_ret = round(sum(kc_investment_ret_df['investment_returns']), 2)
profit_percentage = floor((total_investment_ret/investment_value)*100)
print(cl('Profit gained from the KC strategy by investing $100k in NVDA : {}'.format(total_investment_ret), attrs = ['bold']))
print(cl('Profit percentage of the KC strategy : {}%'.format(profit_percentage), attrs = ['bold']))

Profit gained from the KC strategy by investing $100k in NVDA : 123390.46
Profit percentage of the KC strategy : 123%
  • Comparing the expected return against the SPY benchmark [3]
# SPY ETF COMPARISON

def get_benchmark(start_date, investment_value):
    spy = get_historical_data('SPY', start_date)['close']
    benchmark = pd.DataFrame(np.diff(spy)).rename(columns = {0:'benchmark_returns'})
    
    investment_value = investment_value
    number_of_stocks = floor(investment_value/spy.iloc[-1])
    benchmark_investment_ret = []
    
    for i in range(len(benchmark['benchmark_returns'])):
        returns = number_of_stocks*benchmark['benchmark_returns'].iloc[i]
        benchmark_investment_ret.append(returns)

    benchmark_investment_ret_df = pd.DataFrame(benchmark_investment_ret).rename(columns = {0:'investment_returns'})
    return benchmark_investment_ret_df

benchmark = get_benchmark('2022-01-01', 100000)
investment_value = 100000
total_benchmark_investment_ret = round(sum(benchmark['investment_returns']), 2)
benchmark_profit_percentage = floor((total_benchmark_investment_ret/investment_value)*100)
print(cl('Benchmark profit by investing $100k : {}'.format(total_benchmark_investment_ret), attrs = ['bold']))
print(cl('Benchmark Profit percentage : {}%'.format(benchmark_profit_percentage), attrs = ['bold']))
print(cl('KC Strategy profit is {}% higher than the Benchmark Profit'.format(profit_percentage - benchmark_profit_percentage), attrs = ['bold']))

Benchmark profit by investing $100k : 14942.34
Benchmark Profit percentage : 14%
KC Strategy profit is 109% higher than the Benchmark Profit

Inferences:

  • We can see that the KC delivers powerful breakouts signals. The incorporation of ATR ensures that the KC is more effective when volatility rises [6].
  • Traders can join a bullish breakout early when there is a pullback to the outside of the upper line. Likewise, when a bearish breakout occurs, traders can seek to place aggressive sell orders when the price pulls back to the outside of the lower line [6].
  • The expected ROI of the KC trading strategy by investing $100k in NVDA is 123%.
  • Results show that the KC trading strategy has outperformed the SPY ETF by 109%.

Bottom Line:

  • The KC is a volatility-based indicator that combines two outer bands, representing the ATR of an asset’s prices, with a central band that follows a 20-period EMA of the asset’s price [6, 7].
  • Traders use this indicator to identify trends, recognize oversold and overbought conditions, and generate trading signals [7].

NVDA VWAP Support & Resistance

  • Let’s look at a moving average indicator called the volume weight average price (VWAP) [8, 9].
  • The VWAP is calculated by taking the average of the high, low, and close for the time period and then weighting that average price by the total volume traded for that period.
  • Calculating and plotting the VWAP for DAT1 [8]
# Calculate VWAP
data=intc.copy()

data.tail()

           open      high      low    close  volume
datetime     
2024-08-19 124.28000 130.00000 123.42 130.00 318333600.0
2024-08-20 128.39999 129.88000 125.89 127.25 300087400.0
2024-08-21 127.32000 129.35001 126.66 128.50 257883600.0
2024-08-22 130.02000 130.75000 123.10 123.74 376189100.0
2024-08-23 125.86000 129.60001 125.22 129.37 322154200.0

print(intc.index)

DatetimeIndex(['2022-01-03', '2022-01-04', '2022-01-05', '2022-01-06',
               '2022-01-07', '2022-01-10', '2022-01-11', '2022-01-12',
               '2022-01-13', '2022-01-14',
               ...
               '2024-08-12', '2024-08-13', '2024-08-14', '2024-08-15',
               '2024-08-16', '2024-08-19', '2024-08-20', '2024-08-21',
               '2024-08-22', '2024-08-23'],
              dtype='datetime64[ns]', name='datetime', length=664, freq=None)

intc['VWAP'] = (((data['high'] + data['low'] + data['close']) / 3) * data['volume']).cumsum() / data['volume'].cumsum()

# Check for crossovers in the last two rows
last_row = intc.iloc[-1]
second_last_row = intc.iloc[-2]

if second_last_row['close'] > second_last_row['VWAP'] and last_row['close'] < last_row['VWAP']:
    print('Price Cross Below VWAP')
elif second_last_row['close'] < second_last_row['VWAP'] and last_row['close'] > last_row['VWAP']:
    print('Price Cross Above VWAP')
else:
    print('No Crossover')

No Crossover

intc['VWAP'].plot(title='NVDA VWAP vs Close',figsize=(12, 6))
intc['close'].plot(figsize=(10, 6))
plt.legend(['VWAP', 'Close'])
NVDA VWAP vs Close price

Inferences:

  • A stock trading above the VWAP as the line rises indicates an uptrend and vice versa on a downtrend.
  • VWAP is a simple line that acts as a support if the stock is trading above it (cf. 2023–2024) and a resistance if the stock is trading below it (cf. 2022).

NVDA Fundamental Analysis

  • Fundamental and technical analysis are two common ways to sort and pick stocks [10].
  • For example, a trader might use fundamental metrics to select the candidate and technical indicators to identify a specific entry or exit price.
  • Let’s learn how to use financial statements to decide if NVDA is a good investment.
  • Reading the company info
import yfinance as yf
nvda=yf.Ticker("NVDA")
print(nvda.info)

{'address1': '2788 San Tomas Expressway', 'city': 'Santa Clara', 'state': 'CA', 'zip': '95051', 'country': 'United States', 'phone': '408 486 2000', 'website': 'https://www.nvidia.com', 'industry': 'Semiconductors', 'industryKey': 'semiconductors', 'industryDisp': 'Semiconductors', 'sector': 'Technology',
..........

'revenueGrowth': 2.621, 'grossMargins': 0.75286, 'ebitdaMargins': 0.61768, 'operatingMargins': 0.64925003, 'financialCurrency': 'USD', 'trailingPegRatio': 1.4497}
  • Reading the nvda.info dictionary keys
nvda_info=nvda.info
nvda_info.keys()

dict_keys(['address1', 'city', 'state', 'zip', 'country', 'phone', 'website', 'industry', 'industryKey', 'industryDisp', 'sector', 'sectorKey', 'sectorDisp', 'longBusinessSummary', 'fullTimeEmployees', 'companyOfficers', 'auditRisk', 'boardRisk', 'compensationRisk', 'shareHolderRightsRisk', 'overallRisk', 'governanceEpochDate', 'compensationAsOfEpochDate', 'irWebsite', 'maxAge', 'priceHint', 'previousClose', 'open', 'dayLow', 'dayHigh', 'regularMarketPreviousClose', 'regularMarketOpen', 'regularMarketDayLow', 'regularMarketDayHigh', 'dividendRate', 'dividendYield', 'exDividendDate', 'payoutRatio', 'fiveYearAvgDividendYield', 'beta', 'trailingPE', 'forwardPE', 'volume', 'regularMarketVolume', 'averageVolume', 'averageVolume10days', 'averageDailyVolume10Day', 'bid', 'ask', 'bidSize', 'askSize', 'marketCap', 'fiftyTwoWeekLow', 'fiftyTwoWeekHigh', 'priceToSalesTrailing12Months', 'fiftyDayAverage', 'twoHundredDayAverage', 'trailingAnnualDividendRate', 'trailingAnnualDividendYield', 'currency', 'enterpriseValue', 'profitMargins', 'floatShares', 'sharesOutstanding', 'sharesShort', 'sharesShortPriorMonth', 'sharesShortPreviousMonthDate', 'dateShortInterest', 'sharesPercentSharesOut', 'heldPercentInsiders', 'heldPercentInstitutions', 'shortRatio', 'shortPercentOfFloat', 'impliedSharesOutstanding', 'bookValue', 'priceToBook', 'lastFiscalYearEnd', 'nextFiscalYearEnd', 'mostRecentQuarter', 'earningsQuarterlyGrowth', 'netIncomeToCommon', 'trailingEps', 'forwardEps', 'pegRatio', 'lastSplitFactor', 'lastSplitDate', 'enterpriseToRevenue', 'enterpriseToEbitda', '52WeekChange', 'SandP52WeekChange', 'lastDividendValue', 'lastDividendDate', 'exchange', 'quoteType', 'symbol', 'underlyingSymbol', 'shortName', 'longName', 'firstTradeDateEpochUtc', 'timeZoneFullName', 'timeZoneShortName', 'uuid', 'messageBoardId', 'gmtOffSetMilliseconds', 'currentPrice', 'targetHighPrice', 'targetLowPrice', 'targetMeanPrice', 'targetMedianPrice', 'recommendationMean', 'recommendationKey', 'numberOfAnalystOpinions', 'totalCash', 'totalCashPerShare', 'ebitda', 'totalDebt', 'quickRatio', 'currentRatio', 'totalRevenue', 'debtToEquity', 'revenuePerShare', 'returnOnAssets', 'returnOnEquity', 'freeCashflow', 'operatingCashflow', 'earningsGrowth', 'revenueGrowth', 'grossMargins', 'ebitdaMargins', 'operatingMargins', 'financialCurrency', 'trailingPegRatio'])
  • Examining the key fundamental KPIs
nvda_info['beta']
1.68

#As of today (2024-08-19), NVIDIA's Beta is 2.49.

nvda_info['trailingPegRatio']
1.4497

nvda_info['earningsGrowth']
6.5

nvda_info['totalRevenue']
79773999104

nvda_info['debtToEquity']
22.866

nvda_info['revenuePerShare']
3.234

nvda_info['ebitda']
49274998784

nvda_info['overallRisk']
8
  • Printing the NVDA financial statement
rio=yf.Ticker("NVDA")
rio.financials 
NVDA Financial Statement 2021–2024
NVDA Financial Statement 2021–2024 (Continued).

Inferences:

  • The beta is 1.68, so NVIDIA’s price volatility has been higher than the market average.
  • The Risk Score for NVIDIA Corp. is significantly higher than its peer group’s. This means that NVIDIA Corp. is significantly less risky than its peer group.
  • Conventional wisdom says that a PEG ratio of 1 or less is considered good (at par or undervalued to its growth rate). A value greater than 1, in general, is not as good (overvalued to its growth rate).
  • NVIDIA 2024 annual Diluted EPS was $1.19, a 585.63% increase from 2023.
  • NVDA Debt/Equity (D/E) Ratio is ~0.2, whereas INTC D/E ~ 0.46 (April 28, 2024).
  • NVDA Revenue Per Share (ttm) is 3.23.

Backtesting NVDA Williams %R & MACD Trading Strategy

  • This section is about combining the Williams %R (WR) and MACD indicators to create a hybrid WR-MACD trading strategy that maximizes the expected returns by avoiding false trading signals [11–13].
  • Williams %R is a momentum indicator that oscillates from 0 to -100. Readings from 0 to -20 are considered overbought. Readings from -80 to -100 are considered oversold. This indicator determines the market’s current level in relation to the lookback period’s highest high.
  • The MACD indicator can help traders identify significant changes in momentum and market sentiment, providing insights for entering and exiting a trade. The lines on the histogram oscillate around the zero line and that gives the MACD the characteristic of the oscillator. The deeper or higher the lines, the stronger the move in price.
  • When the MACD lines are above the zero horizontal, the market can be said to be bullish, and when they are below, we are in bearish mode.
  • When the signal line crosses the MACD line, the indicator is said to be generating a potential buy (above signal line) or sell indication (below signal line).
  • Reading the input stock data (being referred to as DAT2)
# IMPORTING PACKAGES

import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
import requests
from math import floor
from termcolor import colored as cl

plt.style.use('fivethirtyeight')
plt.rcParams['figure.figsize'] = (12,6)

# EXTRACTING STOCK DATA

import pandas as pd
import numpy as np
import requests
import matplotlib.pyplot as plt
from math import floor
from termcolor import colored as cl

plt.style.use('fivethirtyeight')
plt.rcParams['figure.figsize'] = (12,6)

def get_historical_data(symbol, start_date):
    api_key = 'YOUR API KEY'
    api_url = f'https://api.twelvedata.com/time_series?symbol={symbol}&interval=1day&outputsize=5000&apikey={api_key}'
    raw_df = requests.get(api_url).json()
    df = pd.DataFrame(raw_df['values']).iloc[::-1].set_index('datetime').astype(float)
    df = df[df.index >= start_date]
    df.index = pd.to_datetime(df.index)
    return df

aapl = get_historical_data('NVDA', '2021-01-01')
aapl.tail()

           open      high      low    close  volume
datetime     
2024-08-22 130.02000 130.75000 123.10 123.74 376189100.0
2024-08-23 125.86000 129.60001 125.22 129.37 323230300.0
2024-08-26 129.57001 131.25999 124.37 126.46 331964700.0
2024-08-27 125.05000 129.20000 123.88 128.30 301726100.0
2024-08-28 128.12000 128.33000 122.64 124.85 266495961.0
  • Calculating and plotting the Williams %R and MACD indicators [13]
# WILLIAMS %R CALCULATION

def get_wr(high, low, close, lookback):
    highh = high.rolling(lookback).max() 
    lowl = low.rolling(lookback).min()
    wr = -100 * ((highh - close) / (highh - lowl))
    return wr

aapl['wr_14'] = get_wr(aapl['high'], aapl['low'], aapl['close'], 14)
aapl.tail()

# WILLIAMS %R PLOT

plot_data = aapl[aapl.index >= '2021-01-01']

ax1 = plt.subplot2grid((11,1), (0,0), rowspan = 5, colspan = 1)
ax2 = plt.subplot2grid((11,1), (6,0), rowspan = 5, colspan = 1)
ax1.plot(plot_data['close'], linewidth = 2)
ax1.set_title('NVIDIA CLOSING PRICE')
ax2.plot(plot_data['wr_14'], color = 'orange', linewidth = 2)
ax2.axhline(-20, linewidth = 1.5, linestyle = '--', color = 'grey')
ax2.axhline(-80, linewidth = 1.5, linestyle = '--', color = 'grey')
ax2.set_title('NVIDIA WILLIAMS %R 14')
plt.show()

# MACD CALCULATION

def get_macd(price, slow, fast, smooth):
    exp1 = price.ewm(span = fast, adjust = False).mean()
    exp2 = price.ewm(span = slow, adjust = False).mean()
    macd = pd.DataFrame(exp1 - exp2).rename(columns = {'close':'macd'})
    signal = pd.DataFrame(macd.ewm(span = smooth, adjust = False).mean()).rename(columns = {'macd':'signal'})
    hist = pd.DataFrame(macd['macd'] - signal['signal']).rename(columns = {0:'hist'})
    return macd, signal, hist

aapl['macd'] = get_macd(aapl['close'], 26, 12, 9)[0]
aapl['macd_signal'] = get_macd(aapl['close'], 26, 12, 9)[1]
aapl['macd_hist'] = get_macd(aapl['close'], 26, 12, 9)[2]
aapl = aapl.dropna()
aapl.tail()

# MACD PLOT

plot_data = aapl[aapl.index >= '2021-01-01']

def plot_macd(prices, macd, signal, hist):
    ax1 = plt.subplot2grid((11,1), (0,0), rowspan = 5, colspan = 1)
    ax2 = plt.subplot2grid((11,1), (6,0), rowspan = 5, colspan = 1)

    ax1.plot(prices)
    ax1.set_title('NVIDIA STOCK PRICES')
    ax2.plot(macd, color = 'grey', linewidth = 1.5, label = 'MACD')
    ax2.plot(signal, color = 'skyblue', linewidth = 1.5, label = 'SIGNAL')
    ax2.set_title('NVIDIA MACD 26,12,9')

    for i in range(len(prices)):
        if str(hist[i])[0] == '-':
            ax2.bar(prices.index[i], hist[i], color = '#ef5350')
        else:
            ax2.bar(prices.index[i], hist[i], color = '#26a69a')

    plt.legend(loc = 'upper left')

plot_macd(plot_data['close'], plot_data['macd'], plot_data['macd_signal'], plot_data['macd_hist'])
NVDA Williams %R Indicator vs Close Price
NVDA MACD Indicator vs Close Price
  • Implementing the hybrid trading strategy and plotting the expected returns [13]
# TRADING STRATEGY

def implement_wr_macd_strategy(prices, wr, macd, macd_signal):    
    buy_price = []
    sell_price = []
    wr_macd_signal = []
    signal = 0

    for i in range(len(wr)):
        if macd[i] > macd_signal[i]:
            if signal != 1:
                buy_price.append(prices[i])
                sell_price.append(np.nan)
                signal = 1
                wr_macd_signal.append(signal)
            else:
                buy_price.append(np.nan)
                sell_price.append(np.nan)
                wr_macd_signal.append(0)
                
        elif macd[i] < macd_signal[i]:
            if signal != -1:
                buy_price.append(np.nan)
                sell_price.append(prices[i])
                signal = -1
                wr_macd_signal.append(signal)
            else:
                buy_price.append(np.nan)
                sell_price.append(np.nan)
                wr_macd_signal.append(0)
        
        else:
            buy_price.append(np.nan)
            sell_price.append(np.nan)
            wr_macd_signal.append(0)
            
    return buy_price, sell_price, wr_macd_signal
            
buy_price, sell_price, wr_macd_signal = implement_wr_macd_strategy(aapl['close'], aapl['wr_14'], aapl['macd'], aapl['macd_signal'])

# POSITION

position = []
for i in range(len(wr_macd_signal)):
    if wr_macd_signal[i] > 1:
        position.append(0)
    else:
        position.append(1)
        
for i in range(len(aapl['close'])):
    if wr_macd_signal[i] == 1:
        position[i] = 1
    elif wr_macd_signal[i] == -1:
        position[i] = 0
    else:
        position[i] = position[i-1]
        
close_price = aapl['close']
wr = aapl['wr_14']
macd_line = aapl['macd']
signal_line = aapl['macd_signal']
wr_macd_signal = pd.DataFrame(wr_macd_signal).rename(columns = {0:'wr_macd_signal'}).set_index(aapl.index)
position = pd.DataFrame(position).rename(columns = {0:'wr_macd_position'}).set_index(aapl.index)

frames = [close_price, wr, macd_line, signal_line, wr_macd_signal, position]
strategy = pd.concat(frames, join = 'inner', axis = 1)

strategy.head()

rets = aapl.close.pct_change().dropna()
strat_rets = strategy.wr_macd_position[1:]*rets

plt.title('NVIDIA Daily Returns')
rets.plot(color = 'blue', alpha = 0.3, linewidth = 7)
strat_rets.plot(color = 'r', linewidth = 1)
plt.show()
NVIDIA Daily Returns (blue line) vs Hybrid Strategy Returns (red line)
rets_cum = (1 + rets).cumprod() - 1 
strat_cum = (1 + strat_rets).cumprod() - 1

plt.title('Cumulative Returns')
rets_cum.plot(color = 'blue', alpha = 0.3, linewidth = 7)
strat_cum.plot(color = 'r', linewidth = 2)
plt.show()
NVIDIA Cumulative Returns (blue line) vs Hybrid Strategy Cumulative Returns (red line)
  • Backtesting the hybrid strategy
aapl_ret = pd.DataFrame(np.diff(aapl['close'])).rename(columns = {0:'returns'})
adx_strategy_ret = []

for i in range(len(aapl_ret)):
    returns = aapl_ret['returns'][i]*strategy['wr_macd_position'][i]
    adx_strategy_ret.append(returns)
    
adx_strategy_ret_df = pd.DataFrame(adx_strategy_ret).rename(columns = {0:'returns'})
investment_value = 10000
number_of_stocks = floor(investment_value/aapl['close'][-1])
adx_investment_ret = []

for i in range(len(adx_strategy_ret_df['returns'])):
    returns = number_of_stocks*adx_strategy_ret_df['returns'][i]
    adx_investment_ret.append(returns)

adx_investment_ret_df = pd.DataFrame(adx_investment_ret).rename(columns = {0:'investment_returns'})
total_investment_ret = round(sum(adx_investment_ret_df['investment_returns']), 2)
profit_percentage = floor((total_investment_ret/investment_value)*100)
print(cl('Profit gained from the WR-MACD strategy by investing $10k in NVDA: {}'.format(total_investment_ret), attrs = ['bold']))
print(cl('Profit percentage of the WR-MACD strategy : {}%'.format(profit_percentage), attrs = ['bold']))

Profit gained from the WR-MACD strategy by investing $10k in NVDA: 6991.62
Profit percentage of the WR-MACD strategy : 69%
  • Comparing strategy profits to industry averages (SPY)
def get_benchmark(start_date, investment_value):
    spy = get_historical_data('SPY', start_date)['close']
    benchmark = pd.DataFrame(np.diff(spy)).rename(columns = {0:'benchmark_returns'})
    
    investment_value = investment_value
    number_of_stocks = floor(investment_value/spy[-1])
    benchmark_investment_ret = []
    
    for i in range(len(benchmark['benchmark_returns'])):
        returns = number_of_stocks*benchmark['benchmark_returns'][i]
        benchmark_investment_ret.append(returns)

    benchmark_investment_ret_df = pd.DataFrame(benchmark_investment_ret).rename(columns = {0:'investment_returns'})
    return benchmark_investment_ret_df

benchmark = get_benchmark('2021-01-01', 10000)

investment_value = 10000
total_benchmark_investment_ret = round(sum(benchmark['investment_returns']), 2)
benchmark_profit_percentage = floor((total_benchmark_investment_ret/investment_value)*100)
print(cl('Benchmark profit by investing $10k : {}'.format(total_benchmark_investment_ret), attrs = ['bold']))
print(cl('Benchmark Profit percentage : {}%'.format(benchmark_profit_percentage), attrs = ['bold']))
print(cl('WR-MACD Strategy profit is {}% higher than the Benchmark Profit'.format(profit_percentage - benchmark_profit_percentage), attrs = ['bold']))

Benchmark profit by investing $10k : 3178.83
Benchmark Profit percentage : 31%
WR-MACD Strategy profit is 38% higher than the Benchmark Profit

Inferences:

Profit gained from the WR-MACD strategy by investing $10k in NVDA: 6991.62
Profit percentage of the WR-MACD strategy : 69%
Benchmark profit by investing $10k : 3178.83
Benchmark Profit percentage : 31%
WR-MACD Strategy profit is 38% higher than the Benchmark Profit
  • Backtesting the WR-MACD strategy is important because it allows us to test this strategy on historical data (viz. DAT2) to evaluate its viability before risking capital in real-time trading.
  • However, past data isn’t necessarily a good predictor of future market behavior, so no strategy can guarantee accuracy.
  • Also, backtesting is different from scenario analysis and the forward performance approach to testing the effectiveness of a given trading strategy.

Monte Carlo Simulation of NVDA Value at Risk (VaR)

  • In this section, we’ll download the NVDA historical data and calculate Value at Risk (VaR) by running the Monte Carlo simulation [14, 15].
  • VaR quantifies the potential loss in value of an asset over a defined period for a given confidence interval [15].
  • Importing Python libraries and downloading the NVDA stock data
import numpy
import pandas
import scipy.stats
import matplotlib.pyplot as plt
import requests
from math import floor
from termcolor import colored as cl
import numpy as np
import pandas as pd
plt.style.use("bmh")
from datetime import datetime

def get_historical_data(symbol, start_date):
    api_key = 'YOUR API KEY'
    api_url = f'https://api.twelvedata.com/time_series?symbol={symbol}&interval=1day&outputsize=5000&apikey={api_key}'
    raw_df = requests.get(api_url).json()
    df = pd.DataFrame(raw_df['values']).iloc[::-1].set_index('datetime').astype(float)
    df = df[df.index >= start_date]
    df.index = pd.to_datetime(df.index)
    return df

nvd = get_historical_data('NVDA', '2022-01-03')
nvd.tail()

           open      high      low    close   volume
datetime     
2024-08-23 125.86000 129.60001 125.22 129.370 323230300.0
2024-08-26 129.57001 131.25999 124.37 126.460 331964700.0
2024-08-27 125.05000 129.20000 123.88 128.300 303134600.0
2024-08-28 128.12000 128.33000 122.64 125.610 437643900.0
2024-08-29 121.35500 124.11000 119.23 121.255 245155865.0
  • Plotting the NVDA close price
fig = plt.figure()
fig.set_size_inches(10,3)
nvd["close"].plot()
plt.title("NVDA stock", weight="bold");
NVDA close price
  • Calculating the NVDA daily returns
fig = plt.figure()
fig.set_size_inches(10,3)
nvd["close"].pct_change().plot()
plt.title("NVDA daily returns", weight="bold");
NVDA daily returns
  • Plotting the histogram of NVDA daily returns and calculating std
nvd["close"].pct_change().hist(bins=50, density=True, histtype="stepfilled", alpha=0.5)
plt.title("Histogram of NVDA daily returns", weight="bold")
nvd["close"].pct_change().std()

0.03567997781131377
Histogram of NVDA daily returns
  • Calculating the normal probability Q-Q plot of NVDA daily returns
Q = nvd["close"].pct_change().dropna()
scipy.stats.probplot(Q, dist=scipy.stats.norm, plot=plt.figure().add_subplot(111))
plt.title("Normal probability plot of NVDA daily returns", weight="bold");
Normal probability Q-Q plot of NVDA daily returns
  • Calculating the student probability Q-Q plot of NVDA daily returns
tdf, tmean, tsigma = scipy.stats.t.fit(Q)
scipy.stats.probplot(Q, dist=scipy.stats.t, sparams=(tdf, tmean, tsigma), plot=plt.figure().add_subplot(111))
plt.title("Student probability plot of NVDA daily returns", weight="bold");
Student probability Q-Q plot of NVDA daily returns
  • Calculating empirical quantiles from a histogram of daily returns
returns = nvd["close"].pct_change().dropna()
mean = returns.mean()
sigma = returns.std()
tdf, tmean, tsigma = scipy.stats.t.fit(returns)
returns.hist(bins=40, density=True, histtype="stepfilled", alpha=0.5)
plt.title("NVDA Daily Returns", weight="bold");


returns.quantile(0.05)

-0.05528745219087147

print (mean,sigma)

0.0027153437382872237 0.03567997781131377

scipy.stats.norm.ppf(0.05, mean, sigma)

-0.055972997174200304
Empirical Distribution of NVDA Daily Returns
  • Plotting the PDF approximation of the above empirical distribution
support = numpy.linspace(returns.min(), returns.max(), 100)
returns.hist(bins=40, density=True, histtype="stepfilled", alpha=0.5);
plt.plot(support, scipy.stats.t.pdf(support, loc=tmean, scale=tsigma, df=tdf), "r-")
plt.title("Daily change in NVDA (%)", weight="bold");
PDF approximation of NVDA daily returns
  • Running the following Monte Carlo simulation using the function random_walk. This function simulates one stock market evolution, and returns the price evolution as an array. It simulates geometric Brownian motion using pseudorandom numbers drawn from a normal distribution [14].
days = 300   # time horizon
dt = 1/float(days)
sigma = 0.03567997781131377 # volatility
mu = 0.0027153437382872237  # drift (average growth rate)
startprice=121.255

def random_walk(startprice):
    price = numpy.zeros(days)
    shock = numpy.zeros(days)
    price[0] = startprice
    for i in range(1, days):
        shock[i] = numpy.random.normal(loc=mu * dt, scale=sigma * numpy.sqrt(dt))
        price[i] = max(0, price[i-1] + shock[i] * price[i-1])
    return price

plt.figure(figsize=(9,4))    
for run in range(300):
    plt.plot(random_walk(startprice))
plt.xlabel("Days")
plt.ylabel("Price USD");

runs = 10000
simulations = numpy.zeros(runs)
for run in range(runs):
    simulations[run] = random_walk(startprice)[days-1]
q = numpy.percentile(simulations, 1)
plt.hist(simulations, density=True, bins=30, histtype="stepfilled", alpha=0.5)
plt.figtext(0.6, 0.8, "Start price: $121.255")
plt.figtext(0.6, 0.7, "Mean final price: ${:.3}".format(simulations.mean()))
plt.figtext(0.6, 0.6, "VaR(0.99): ${:.3}".format(10 - q))
plt.figtext(0.15, 0.6, "q(0.99): ${:.3}".format(q))
plt.axvline(x=q, linewidth=4, color="r")
plt.title("Final price distribution after {} days".format(days), weight="bold");
Monte Carlo Simulation of NVDA Price USD
Final Price Distribution after 300 Days: Mean final price, VaR(0.99) & q(0.99)

Inferences:

  • Student’s t distribution seems to fit better than the normal distribution (cf. the r.h.s. tail of the distribution).
  • The 0.05 empirical quantile of daily returns is at -0.055. That means that with 95% confidence, our worst daily loss will not exceed 5.5%. If we have a $1 M investment, our one-day 5% VaR is 0.055 * $1 M = $55 k.
  • Final price is spread out between $110 (our portfolio has lost value) to almost $134. We can see that the expectation (mean outcome) is a profit; this is due to the fact that the drift in our random walk (parameter mu) is positive.
  • Our big Monte Carlo simulation of random walks yields the probability distribution of the final price and quantile measures for the estimated VaR.
  • We have looked at the 1% empirical quantile of the final price distribution to estimate the VaR, which is $102 for a $121.255 investment.

Read more here.

NVDA RSI & Bollinger Bands Support-Resistance

import pandas as pd
import matplotlib.pyplot as plt
import numpy as np
import requests
from termcolor import colored as cl
from math import floor
# Import seaborn
import seaborn as sns

plt.style.use('fivethirtyeight')
plt.rcParams['figure.figsize'] = (12,6)

# EXTRACTING STOCK DATA

def get_historical_data(symbol, start_date):
    api_key = 'a07d718849d64be78e8a7d5669e4e3af'
    api_url = f'https://api.twelvedata.com/time_series?symbol={symbol}&interval=1day&outputsize=5000&apikey={api_key}'
    raw_df = requests.get(api_url).json()
    df = pd.DataFrame(raw_df['values']).iloc[::-1].set_index('datetime').astype(float)
    df = df[df.index >= start_date]
    df.index = pd.to_datetime(df.index)
    return df

ticker='NVDA'
start='2023-01-01'

stock = get_historical_data(ticker, start)
stock.tail()

           open      high      low     close volume
datetime     
2024-08-23 125.86000 129.60001 125.22 129.37 323230300.0
2024-08-26 129.57001 131.25999 124.37 126.46 331964700.0
2024-08-27 125.05000 129.20000 123.88 128.30 303134600.0
2024-08-28 128.12000 128.33000 122.64 125.61 448101100.0
2024-08-29 121.36000 124.43000 116.71 117.59 451799100.0

stock['Date']=stock.index
  • Calculating the RSI and Bollinger Bands indicators [17]
#Calculate relative strength of stock in time period (days)
def calculate_relative_strength(data, time_period):
    
    #set date as index
    data = data.set_index(data['Date'])
    
    #create delta
    delta = data['close'].diff(1)
    
    #create gains and loss variables 
    up = delta.copy()
    down = delta.copy()
    
    #conditional to set delta gain
    up[up < 0] = 0
    down[down > 0] = 0
    
    #get average of gains
    AVG_gain = up.rolling(window = time_period).mean()
    
    #get average of loss
    AVG_loss = abs(down.rolling(window = time_period).mean())
    return (AVG_gain, AVG_loss)

#Calculates RSI
def calculate_RSI(AVG_gain, AVG_loss):
   
    # Calculate relative strength
    RS = AVG_gain/AVG_loss
    
    #Calculate relative strength index
    RSI = 100.0-(100.0/(1.0+RS))
    return RSI

#Plot RSI 
def plot_RSI(RSI, tick_symbol):
    
    #set plot sizes
    plt.figure(figsize=(18,8))
    
    #plot RSI values against date index
    plot = sns.lineplot(x = RSI.index, y = RSI.values)
    plot.set_title("RSI: " + tick_symbol)
    plot.set_ylabel('Relative Strength Index')
    
    #plot all levels in RSI
    plot.axhline(30, color = 'green')
    plot.axhline(70, color = 'green')
    plot.axhline(20, color = 'yellow')
    plot.axhline(80, color = 'yellow')
    plot.axhline(10, color = 'red')
    plot.axhline(90, color = 'red')
    
    #Get the last/current RSI
    data = RSI.tail(1)
    
    #If greater than 70, display "Sell"
    if data.values > 70:
        for x,y in zip(data.index,data.values):
            label = "Sell"
            plt.annotate(label, # this is the text
                         (x,y), # this is the point to label
                         textcoords="offset points", # how to position the text
                         xytext=(0,10), # distance from text to points (x,y)
                         ha='center',
                         fontsize = 25) # horizontal alignment can be left, right or center
            plt.scatter(data.index, data.values,label = 'Sell', marker = 'v', color = 'red', alpha = 1, s = 100) #plot scatter on RSI plot
    #If less than 30, display "Buy"
    elif data.values < 30:
        for x,y in zip(data.index,data.values):
            label = "Buy"
            plt.annotate(label, # this is the text
                         (x,y), # this is the point to label
                         textcoords="offset points", # how to position the text
                         xytext=(0,10), # distance from text to points (x,y)
                         ha='center',
                         fontsize = 25) # horizontal alignment can be left, right or center
            plt.scatter(data.index, data.values, label = 'Buy', marker = '^', color = 'green', alpha = 1, s = 100) #plot scatter on RSI plot

#Combine all smaller elements to single function
def RSI(data, tick_symbol, time_period):
        gain, loss = calculate_relative_strength(data, time_period)
        RSI = calculate_RSI(gain, loss)
        plot_RSI(RSI, tick_symbol)

# Produces the bollinger bands for a stock 
def Bollinger_Band(data, period, ticker):
        plt.figure(figsize=(18,8))
        data['bollinger_first'] = data['close'].rolling(period).mean() 
        #Use formula
        data['bollinger_second'] = data['close'].rolling(period).mean() + 2*(data['close'].rolling(period).std())
        data['bollinger_third'] = data['close'].rolling(period).mean() - 2*(data['close'].rolling(period).std())
        
        #signal to determine upper and lower band crossovers 
        buy = []
        sell = []
        for i in range(len(data['close'])):
            if data['close'][i] > data['bollinger_second'][i]:
                buy.append(np.nan)
                sell.append(data['close'][i])
            elif data['close'][i] < data['bollinger_third'][i]:
                buy.append(data['close'][i])
                sell.append(np.nan)
            else:
                buy.append(np.nan)
                sell.append(np.nan)
    
        #plot result 
        graph = sns.lineplot(data = data, x = 'Date', y = 'close')
        sns.lineplot(data = data, x = 'Date', y = 'bollinger_second')
        sns.lineplot(data = data, x = 'Date', y = 'bollinger_first')
        sns.lineplot(data = data, x = 'Date', y = 'bollinger_third')
#        plt.legend(['Close Price', 'Upper Band', 'Middle Band', 'Lower Bound'])
        graph.set_title("Bollinger Bands:" + ticker)
        
        #create columns for buy and sell signals
        data['Buy'] = buy
        data['Sell'] = sell
        
        #obtain non-null records
        data_buy_non_nan = data.loc[data['Buy'].notnull()]
        data_sell_non_nan = data.loc[data['Sell'].notnull()]
        
        #concatenate signals
        signals = pd.concat([data_buy_non_nan, data_sell_non_nan])
        signals = signals.reset_index(drop = True)
        
        #plot signals 

        sns.scatterplot(data=data, x="Date", y="Buy",s=400, c="green", marker="^")
        sns.scatterplot(data=data, x="Date", y="Sell",s=400, c="red", marker="v")
        
        #Add close price on signals 
        for x,y,z in zip(signals['Date'], signals['close'], signals['close']):
            label = z #Label corresponds to labels in dataset
            plt.annotate(label, #text to be displayed
                         (x,y), #point for the specific label
                         textcoords="offset points", #positioning of the text
                         xytext=(0,10), #distance from text to points
                         ha='center',
                         fontsize = 12) #horizontal alignment


#Combine all RSI and Bollinger Band functions into one 
def RSI_Bollinger(data, period_bollinger, period_RSI, ticker):
    fig = plt.figure()
    bollinger = Bollinger_Band(data, period_bollinger, ticker)
    rsi = RSI(data, ticker, period_RSI)
    bollinger
    rsi
    plt.show()
  • Applying RSI with Bollinger Bands to DAT3 and visualize the result
if __name__ == "__main__": 
#    period_RSI = int(input('Time period of RSI (in days)'))
    period_RSI = 14
    #specify period for bollinger 20
#    period_Boll = int(input('Time period of Bollinger Band (in days)'))
    period_Boll=20
    #view results
    RSI_Bollinger(stock, period_Boll, period_RSI, ticker)
NVDA RSI with Bollinger Bands
  • RSI is considered overbought when above 70 and oversold when below 30.
  • The Bollinger Bands widen and narrow when the volatility of the price is higher or lower, respectively.
  • When the price bounces off of the upper band and crosses the middle band, then the lower band becomes the price target.

Inferences:

  • The current NVDA RSI value is below 70 and well above 30, suggesting NVDA is neither overbought nor oversold (i.e. Oversold / Overbought: Neutral / Hold).
  • 20 Day Bollinger Bands also suggest Hold.
  • Widening Bands indicate an increase in price volatility, which often signals the start of a strong trend (as of 2024–08–29).

Risk-Return Comparison of 10 Stocks to Watch

  • Let’s implement the passive investment strategy [19] by computing risks and returns of the following 10 stocks to watch:
symbol =['AMD','GOOGL','WST','DXCM','NVDA','AAPL','AMZN','NFLX','TSLA','MSFT']
  • Fetching and plotting the stock historical data DAT4
import numpy as np
import pandas as pd
from datetime import date
import matplotlib.pyplot as plt
import yfinance as yf
%matplotlib inline

start_date = '2020-01-01'

df = yf.download(symbol, start=start_date)['Close'] #getting only Closing price values 
df.tail()

Ticker AAPL AMD AMZN DXCM GOOGL MSFT NFLX NVDA TSLA WST
Date          
2024-08-26 227.179993 149.990005 175.500000 73.669998 166.160004 413.489990 688.440002 126.459999 213.210007 303.489990
2024-08-27 228.029999 150.500000 173.119995 72.239998 164.679993 413.839996 695.719971 128.300003 209.210007 302.630005
2024-08-28 226.490005 146.360001 170.800003 70.480003 162.850006 410.600006 683.840027 125.610001 205.750000 297.609985
2024-08-29 229.789993 145.490005 172.119995 69.620003 161.779999 413.119995 692.479980 117.589996 206.279999 314.790009
2024-08-30 228.794998 146.804993 173.960007 69.389801 161.919998 414.600006 691.049988 118.459999 208.804993 317.730011

#Charting the stock prices for multiple stocks
fig = plt.figure(figsize=(20,12))
ax1 = fig.add_subplot(4,3,1)
ax2 = fig.add_subplot(4,3,2)
ax3 = fig.add_subplot(4,3,3)
ax4 = fig.add_subplot(4,3,4)
ax5 = fig.add_subplot(4,3,5)
ax6 = fig.add_subplot(4,3,6)
ax7 = fig.add_subplot(4,3,7)
ax8 = fig.add_subplot(4,3,8)
ax9 = fig.add_subplot(4,3,9)
ax10 = fig.add_subplot(4,3,10)
ax1.plot(df['AMZN'])
ax1.set_title("Amazon")
ax2.plot(df['AMD'])
ax2.set_title("AMD")
ax3.plot(df['GOOGL'])
ax3.set_title("Google")
ax4.plot(df['NFLX'])
ax4.set_title("Netflix")
ax5.plot(df['WST'])
ax5.set_title("WST")
ax6.plot(df['DXCM'])
ax6.set_title("DXCM")
ax7.plot(df['NVDA'])
ax7.set_title("NVDA")
ax8.plot(df['TSLA'])
ax8.set_title("Tesla")
ax9.plot(df['AAPL'])
ax9.set_title("AAPL")
ax10.plot(df['MSFT'])
ax10.set_title("MSFT")
plt.tight_layout()
plt.show()
Close Price of 10 stocks to watch
  • Calculating portfolio return of the hold strategy
log_returns_stock=np.exp(log_returns.sum())#total return of hold strategy
log_returns_stock

Ticker
AAPL      3.047045
AMD       2.989918
AMZN      1.833078
DXCM      1.265198
GOOGL     2.366075
MSFT      2.581248
NFLX      2.095297
NVDA     19.750740
TSLA      7.279494
WST       2.093911
dtype: float64

portfolio_return=log_returns_stock.mean(axis=0)
print("Portfolio return using hold strategy: {:>10.2%}".format(portfolio_return))

Portfolio return using hold strategy:    453.02%

fig, ax= plt.subplots(figsize=(12,8))
for c in log_returns:
    ax.plot(log_returns.index, 100*(np.exp(log_returns[c].cumsum()) - 1), label=str(c))
ax.set_ylabel('Total relative returns (%)')
ax.set_xlabel('Time period')
ax.set_title('Portfolio return using hold strategy')
ax.legend(loc='best')
plt.show()
Portfolio return using hold strategy
  • Comparing asset risks and returns of the hold strategy
returns = log_returns #using log_return 
plt.figure(figsize=(12,6)) 
plt.scatter(returns.mean(), returns.std(),s=100)
plt.xlabel('Expected returns')
plt.ylabel('Risk')
for label, x, y in zip(returns.columns, returns.mean(), returns.std()):
    plt.annotate(
        label, 
        xy = (x, y), xytext = (50, 30),
        textcoords = 'offset points', ha = 'right', va = 'bottom',
        arrowprops=dict(arrowstyle= '<|-|>',
                             color='blue',
                             lw=1,
                             ls='--'))
Asset risks and returns of the hold strategy
  • Calculating returns of the SMA 50–200 trading strategy [19]
# Calculating the short-window moving average for entire portfolio
short_rolling= df.rolling(window=50,min_periods=1).mean()
df_short = short_rolling.add_suffix('_short')
# Calculating the long-window moving average for entire portfolio
long_rolling = df.rolling(window=200,min_periods=1).mean()
df_long = long_rolling.add_suffix('_long')
#to join two tables into one df 
new_df=pd.concat([df_short,df_long],axis=1)
#calculating trading for entire portfolio
new_df['AMD_Trade'] = np.where(new_df['AMD_short']> new_df['AMD_long'], 1, -1)
new_df['AMZN_Trade'] = np.where(new_df['AMZN_short']> new_df['AMZN_long'], 1, -1)
new_df['AAPL_Trade'] = np.where(new_df['AAPL_short']> new_df['AAPL_long'], 1, -1)
new_df['DXCM_Trade'] = np.where(new_df['DXCM_short']> new_df['DXCM_long'], 1, -1)
new_df['MSFT_Trade'] = np.where(new_df['MSFT_short']> new_df['MSFT_long'], 1, -1)
new_df['NFLX_Trade'] = np.where(new_df['NFLX_short']> new_df['NFLX_long'], 1, -1)
new_df['NVDA_Trade'] = np.where(new_df['NVDA_short']> new_df['NVDA_long'], 1, -1)
new_df['TSLA_Trade'] = np.where(new_df['TSLA_short']> new_df['TSLA_long'], 1, -1)
new_df['WST_Trade'] = np.where(new_df['WST_short']> new_df['WST_long'], 1, -1)
new_df['GOOGL_Trade'] = np.where(new_df['GOOGL_short']> new_df['GOOGL_long'], 1, -1)
# creating return column
log_returns = np.log(df).diff()
df_return= log_returns.add_suffix('_Return')
new_df=pd.concat([df_return,new_df],axis=1)
#remove NA values
new_df.dropna(inplace=True)
#calculating instatenious rate of return
new_df['AMD_Treturn']=new_df.AMD_Return*new_df.AMD_Trade
AMD_total_treturn=np.exp(new_df.AMD_Treturn.sum())
print('AMD_total_treturn is:',AMD_total_treturn)
new_df['AMZN_Treturn']=new_df.AMZN_Return*new_df.AMZN_Trade
AMZN_total_treturn=np.exp(new_df.AMZN_Treturn.sum())
print('AMZN_total_treturn is:',AMZN_total_treturn)
new_df['AAPL_Treturn']=new_df.AAPL_Return*new_df.AAPL_Trade
AAPL_total_treturn=np.exp(new_df.AAPL_Treturn.sum())
print('AAPL_total_treturn is:',AAPL_total_treturn)
new_df['DXCM_Treturn']=new_df.DXCM_Return*new_df.DXCM_Trade
DXCM_total_treturn=np.exp(new_df.DXCM_Treturn.sum())
print('DXCM_total_treturn is:',DXCM_total_treturn)
new_df['MSFT_Treturn']=new_df.MSFT_Return*new_df.MSFT_Trade
MSFT_total_treturn=np.exp(new_df.MSFT_Treturn.sum())
print('MSFT_total_treturn is:',MSFT_total_treturn)
new_df['NFLX_Treturn']=new_df.NFLX_Return*new_df.NFLX_Trade
NFLX_total_treturn=np.exp(new_df.NFLX_Treturn.sum())
print('NFLX_total_treturn is:',NFLX_total_treturn)
new_df['NVDA_Treturn']=new_df.NVDA_Return*new_df.NVDA_Trade
NVDA_total_treturn=np.exp(new_df.NVDA_Treturn.sum())
print('NVDA_total_treturn is:',NVDA_total_treturn)
new_df['TSLA_Treturn']=new_df.TSLA_Return*new_df.TSLA_Trade
TSLA_total_treturn=np.exp(new_df.TSLA_Treturn.sum())
print('TSLA_total_treturn is:',TSLA_total_treturn)
new_df['WST_Treturn']=new_df.WST_Return*new_df.WST_Trade
WST_total_treturn=np.exp(new_df.WST_Treturn.sum())
print('WST_total_treturn is:',WST_total_treturn)
new_df['GOOGL_Treturn']=new_df.GOOGL_Return*new_df.GOOGL_Trade
GOOGL_total_treturn=np.exp(new_df.GOOGL_Treturn.sum())
print('GOOGL_total_treturn is;',GOOGL_total_treturn)

AMD_total_treturn is: 2.3799707255030196
AMZN_total_treturn is: 3.1633346327179472
AAPL_total_treturn is: 0.8448978146082059
DXCM_total_treturn is: 1.1943644380013865
MSFT_total_treturn is: 2.4911794060806116
NFLX_total_treturn is: 4.234814819819454
NVDA_total_treturn is: 26.215899221614798
TSLA_total_treturn is: 1.7373723016290843
WST_total_treturn is: 1.8016621963389188
GOOGL_total_treturn is; 2.6805537913562865

treturns_total = [AMD_total_treturn,AMZN_total_treturn,AAPL_total_treturn,DXCM_total_treturn,MSFT_total_treturn,NFLX_total_treturn,NVDA_total_treturn,TSLA_total_treturn,WST_total_treturn,GOOGL_total_treturn]
potrfolio_trade_strategy = sum(treturns_total) / len(treturns_total)
print("Portfolio return using trade strategy: {:>10.2%}".format(potrfolio_trade_strategy))

Portfolio return using trade strategy:    467.44%

Inferences:

  • We have implemented moving average based strategies to buy and/or sell stocks for 10 stocks of interest and compared the results with the ones obtained when using the buy and hold strategy.
  • We can see that the SMA 50–200 trading strategy performed better than the buy and hold strategy with 467% and 453% returns, respectively.
  • The Risk-Return plot shows that NVDA has max expected return and medium risk; DXCM has min expected return and medium risk; MSFT has medium expected return and min risk; TSLA has high expected return (second after NVDA) and max risk.
  • This is a part of debate that has been going on for decades. Buy and Hold vs. Timing the Market: Which one is better?

Backtest GOOG RSI-SMA Trading Strategies

!pip install backtesting
  • Reading the GOOG stock data and backtesting the RSI-SMA trading strategies [20]
import pandas as pd

def SMA(array, n):
    """Simple moving average"""
    return pd.Series(array).rolling(n).mean()

def RSI(array, n):
    """Relative strength index"""
    # Approximate; good enough
    gain = pd.Series(array).diff()
    loss = gain.copy()
    gain[gain < 0] = 0
    loss[loss > 0] = 0
    rs = gain.ewm(n).mean() / loss.abs().ewm(n).mean()
    return 100 - 100 / (1 + rs)

import backtesting

from backtesting import Strategy, Backtest
from backtesting.lib import resample_apply


class System(Strategy):
    d_rsi = 30  # Daily RSI lookback periods
    w_rsi = 30  # Weekly
    level = 70
    
    def init(self):
        # Compute moving averages the strategy demands
        self.ma10 = self.I(SMA, self.data.Close, 10)
        self.ma20 = self.I(SMA, self.data.Close, 20)
        self.ma50 = self.I(SMA, self.data.Close, 50)
        self.ma100 = self.I(SMA, self.data.Close, 100)
        
        # Compute daily RSI(30)
        self.daily_rsi = self.I(RSI, self.data.Close, self.d_rsi)
        
        # To construct weekly RSI, we can use `resample_apply()`
        # helper function from the library
        self.weekly_rsi = resample_apply(
            'W-FRI', RSI, self.data.Close, self.w_rsi)
        
        
    def next(self):
        price = self.data.Close[-1]
        
        # If we don't already have a position, and
        # if all conditions are satisfied, enter long.
        if (not self.position and
            self.daily_rsi[-1] > self.level and
            self.weekly_rsi[-1] > self.level and
            self.weekly_rsi[-1] > self.daily_rsi[-1] and
            self.ma10[-1] > self.ma20[-1] > self.ma50[-1] > self.ma100[-1] and
            price > self.ma10[-1]):
            
            # Buy at market price on next open, but do
            # set 8% fixed stop loss.
            self.buy(sl=.92 * price)
        
        # If the price closes 2% or more below 10-day MA
        # close the position, if any.
        elif price < .98 * self.ma10[-1]:
            self.position.close()

import yfinance as yf
GOOGLE=yf.download("GOOG",start="2020-01-01", auto_adjust = True)

from backtesting import Backtest

backtest = Backtest(GOOGLE, System, commission=.002)
stats= backtest.run()

%%time

backtest.optimize(d_rsi=range(10, 35, 5),
                  w_rsi=range(10, 35, 5),
                  level=range(30, 80, 10))

Error displaying widget
CPU times: total: 4.45 s
Wall time: 4.48 s
Start                     2020-01-02 00:00:00
End                       2024-08-30 00:00:00
Duration                   1702 days 00:00:00
Exposure Time [%]                   35.860307
Equity Final [$]                 18418.236525
Equity Peak [$]                  19330.237563
Return [%]                          84.182365
Buy & Hold Return [%]              141.774928
Return (Ann.) [%]                   14.008097
Volatility (Ann.) [%]               17.698778
Sharpe Ratio                         0.791473
Sortino Ratio                        1.422444
Calmar Ratio                         1.178269
Max. Drawdown [%]                   -11.88871
Avg. Drawdown [%]                   -3.297176
Max. Drawdown Duration      617 days 00:00:00
Avg. Drawdown Duration       48 days 00:00:00
# Trades                                   14
Win Rate [%]                             50.0
Best Trade [%]                      16.859706
Worst Trade [%]                     -6.881568
Avg. Trade [%]                       4.478954
Max. Trade Duration         116 days 00:00:00
Avg. Trade Duration          43 days 00:00:00
Profit Factor                        5.857247
Expectancy [%]                       4.737138
SQN                                  2.272172
_strategy                 System(d_rsi=10,...
_equity_curve                             ...
_trades                       Size  EntryB...
dtype: object

backtest.plot()
Backtest GOOG RSI-SMA trading strategies

Inferences:

  • This example shows some of the features of backtesting.py, a Python framework for backtesting trading strategies.

Backtest.run() method returns a pandas Series of simulation results and statistics associated with our strategy.

Backtest.plot() method provides the same insights in a more visual form.

  • We see that this simple strategy makes almost 84% return in the period of 2020–2024, with annual volatility 18%, max drawdown 12%, and with max drawdown period spanning 617 days.
Sortino Ratio                        1.422444
  • A good Sortino ratio typically exceeds 1. It measures the risk-adjusted returns, focusing on downside risk. A higher Sortino ratio indicates better performance in managing downside volatility, which is crucial for risk-averse investors.
Calmar Ratio                         1.178269
  • If the Calamar ratio is more than 1, it indicates that the returns are higher than the drawdown, albeit slightly.
Sharpe Ratio                         0.791473
  • Investments with less than 1.00 Sharpe Ratio do not generate high returns. Contrarily, investments with a Sharpe Ratio of 1.00 to 3.00 or above have higher returns subsequently.

AMD SMA-MACD-ADX Trend-Following Analysis

  • Let’s evaluate statistical trends in the recent AMD trading activity, involving technical indicators such as SMA, MACD [21], and ADX [22]. The idea is to combine these three indicators for a comprehensive trend-following trading strategy.
  • The Simple Moving Average (SMA) indicator smooths price data to reveal the underlying trend. The 200-day SMA is particularly useful for long-term analysis, indicating the overall trend direction.
  • MACD [21], short for moving average convergence/divergence, can help traders identify significant changes in momentum and market sentiment. When the MACD line crosses from below to above the signal line, the indicator is considered bullish. The further below the zero line the stronger the signal. When the MACD line crosses from above to below the signal line, the indicator is considered bearish.
  • Traders use the MACD histogram to identify potential trend reversals and price swings. If MACD is below its signal line, the histogram will be below the MACD’s baseline. Zeroes in the MACD histogram occur when the MACD line crosses higher than the signal line (generally considered a buy signal) or below the signal line (a sell signal). Peaks and troughs in the histogram indicate when a burst of bearish or bullish momentum is losing strength, and the curve is likely to return to its mean.
  • The ADX value typically ranges from 0 to 100. ADX Value vs Trend Strength: 0–25 Absent or Weak Trend; 25–50 Strong Trend; 50–75 Very Strong Trend; 75–100 Extremely Strong Trend.
  • Reading the AMD stock data DAT5 and examining the general data info
import yfinance as yf
ticker = 'AMD'
start_date = dt.datetime(2020,1,1)

df = yf.download(ticker, start=start_date, interval="1d")
df.tail()

           Open       High       Low        Close      Adj Close  Volume
Date      
2024-08-26 154.699997 158.279999 148.910004 149.990005 149.990005 49893300
2024-08-27 150.130005 151.699997 148.440002 150.500000 150.500000 35102700
2024-08-28 149.399994 150.429993 144.720001 146.360001 146.360001 34075800
2024-08-29 146.589996 149.490005 144.470001 145.490005 145.490005 31602100
2024-08-30 147.520004 148.990005 145.250000 148.559998 148.559998 31139700

df.shape
(1174, 6)

df.info()
<class 'pandas.core.frame.DataFrame'>
DatetimeIndex: 1174 entries, 2020-01-02 to 2024-08-30
Data columns (total 6 columns):
 #   Column     Non-Null Count  Dtype  
---  ------     --------------  -----  
 0   Open       1174 non-null   float64
 1   High       1174 non-null   float64
 2   Low        1174 non-null   float64
 3   Close      1174 non-null   float64
 4   Adj Close  1174 non-null   float64
 5   Volume     1174 non-null   int64  
dtypes: float64(5), int64(1)
memory usage: 64.2 KB

df['Close'].describe().T
count    1174.000000
mean      100.648152
std        35.210767
min        38.709999
25%        78.097498
50%        93.584999
75%       118.262499
max       211.380005
Name: Close, dtype: float64
  • Plotting the AMD candlesticks
import mplfinance as mpf
import matplotlib.pyplot as plt

# Plot candlestick chart
mpf.plot(df, type="candle", style="yahoo", title="AMD Stock Price 2020-2024")
The AMD candlestick chart
  • Examining the AMD daily and cumulative returns
amd_daily_returns = df['Adj Close'].pct_change()
amd_daily_returns.plot(title='AMD Daily Returns')
plt.grid()
AMD Daily Returns
amd_daily_returns.hist()

amd_daily_returns.std()
0.0334299238200219

amd_daily_returns.kurt()
2.102226488552409

amd_daily_returns.skew()
0.18632525165901895
Histogram of AMD Daily Returns
amd_cum_returns=amd_daily_returns.add(1).cumprod().sub(1)
amd_cum_returns.plot(title='AMD Cumulative Returns')
plt.grid()
AMD Cumulative Returns
  • Implementing the SMA50–200 crossover strategy [22]
dff = df.copy()
# Calculate 50-day and 200-day moving averages
dff['SMA50'] = dff['Close'].rolling(window=50).mean()
dff['SMA200'] = dff['Close'].rolling(window=200).mean()

# Generate trading signals based on moving average crossover
dff['Signal'] = 0
dff['Signal'][50:] = dff['SMA50'][50:] > dff['SMA200'][50:]
dff['Position'] = dff['Signal'].diff()

plt.figure(figsize=(12, 8))
plt.plot(dff['Close'], label='USD Close Price')
plt.plot(dff['SMA50'], label='50-day SMA')
plt.plot(dff['SMA200'], label='200-day SMA')
plt.plot(dff[dff['Position'] == 1].index, dff['SMA50'][dff['Position'] == 1], '^', markersize=20, color='g', label='Buy signal')
plt.plot(dff[dff['Position'] == -1].index, dff['SMA50'][dff['Position'] == -1], 'v', markersize=20, color='r', label='Sell signal')
plt.title('AMD Simple Moving Average Crossover Strategy')
plt.xlabel('Date')
plt.ylabel('Price')
plt.legend()
plt.show()
AMD Simple Moving Average Crossover Strategy
  • Calculating the AMD MACD (12–26–9) trading indicator [21, 22]
def MACD(ohlc, period_fast = 12, period_slow = 26,signal = 9,column = "Close",adjust = True):
        """
        MACD, MACD Signal and MACD difference.
        The MACD Line oscillates above and below the zero line, which is also known as the centerline.
        These crossovers signal that the 12-day EMA has crossed the 26-day EMA. The direction, of course, depends on the direction of the moving average cross.
        Positive MACD indicates that the 12-day EMA is above the 26-day EMA. Positive values increase as the shorter EMA diverges further from the longer EMA.
        This means upside momentum is increasing. Negative MACD values indicates that the 12-day EMA is below the 26-day EMA.
        Negative values increase as the shorter EMA diverges further below the longer EMA. This means downside momentum is increasing.

        Signal line crossovers are the most common MACD signals. The signal line is a 9-day EMA of the MACD Line.
        As a moving average of the indicator, it trails the MACD and makes it easier to spot MACD turns.
        A bullish crossover occurs when the MACD turns up and crosses above the signal line.
        A bearish crossover occurs when the MACD turns down and crosses below the signal line.
        """

        EMA_fast = pd.Series(
            ohlc[column].ewm(ignore_na=False, span=period_fast, adjust=adjust).mean(),
            name="EMA_fast",
        )
        EMA_slow = pd.Series(
            ohlc[column].ewm(ignore_na=False, span=period_slow, adjust=adjust).mean(),
            name="EMA_slow",
        )
        MACD = pd.Series(EMA_fast - EMA_slow, name="MACD")
        MACD_signal = pd.Series(
            MACD.ewm(ignore_na=False, span=signal, adjust=adjust).mean(), name="SIGNAL"
        )

        return pd.concat([MACD, MACD_signal], axis=1)

plt.figure(figsize=(14,6))
dff[["MACD", "MACD_signal"]]=MACD(dff, period_fast = 12, period_slow = 26,signal = 9,column = "Close",adjust = True)
dff['MACD'].plot(label='MACD')
dff['MACD_signal'].plot(label='MACD_signal')
#plt.ylabel('Price USD')
plt.title('AMD MACD (12-26-9)')
plt.legend()
AMD MACD (12–26–9) trading indicator
  • Plotting the AMD MACD Histogram (12–26–9) [21]
# Calculate MACD
short_term = 12
long_term = 26
signal_period = 9

# Calculate short-term and long-term EMAs
short_ema = dff['Close'].ewm(span=short_term, adjust=False).mean()
long_ema = dff['Close'].ewm(span=long_term, adjust=False).mean()

# Calculate MACD Line
macd_line = short_ema - long_ema

# Calculate Signal Line
signal_line = macd_line.ewm(span=signal_period, adjust=False).mean()

# Calculate MACD Histogram
macd_histogram = macd_line - signal_line

# Add MACD components to the DataFrame
dff['MACD12269'] = macd_line
dff['Signal12269'] = signal_line
dff['Histogram12269'] = macd_histogram

import plotly.graph_objects as go

# Create a Plotly figure
fig = go.Figure()

# MACD and Signal lines
#fig.add_trace(go.Scatter(x=dff.index, y=dff['MACD12269'], mode='lines', name='MACD'))
#fig.add_trace(go.Scatter(x=dff.index, y=dff['Signal12269'], mode='lines', name='Signal'))

# Histogram
fig.add_trace(go.Bar(x=dff.index, y=dff['Histogram12269'], name='Histogram',
                marker_color=['green' if val >= 0 else 'red' for val in dff['Histogram12269']]))

# Customize the chart
fig.update_xaxes(rangeslider=dict(visible=False))
fig.update_layout(plot_bgcolor='#efefff', font_family='Monospace', font_color='#000000', font_size=20,width=400)
fig.update_layout(
        title="AMD MACD Histogram (12-26-9)"
 )

fig.update_layout(
    autosize=False,
    width=1100,
    height=500,
    margin=dict(
        l=50,
        r=50,
        b=100,
        t=100,
        pad=4
    ),
    paper_bgcolor="LightSteelBlue",
)

fig.update_layout(
     xaxis=dict(showgrid=True,gridwidth=1, gridcolor='Grey'), 
     yaxis=dict(showgrid=True,gridwidth=1, gridcolor='Grey')
)
# Show the chart
fig.show()
AMD MACD Histogram (12–26–9)
  • Computing and plotting the AMD ADX20 indicator
def TR(ohlc,high="High",low="Low",close="Close"):
        """True Range is the maximum of three price ranges.
        Most recent period's high minus the most recent period's low.
        Absolute value of the most recent period's high minus the previous close.
        Absolute value of the most recent period's low minus the previous close."""

        TR1 = pd.Series(ohlc[high] - ohlc[low]).abs()  # True Range = High less Low

        TR2 = pd.Series(
            ohlc[high] - ohlc[close].shift()
        ).abs()  # True Range = High less Previous Close

        TR3 = pd.Series(
            ohlc[close].shift() - ohlc[low]
        ).abs()  # True Range = Previous Close less Low

        _TR = pd.concat([TR1, TR2, TR3], axis=1)

        _TR["TR"] = _TR.max(axis=1)

        return pd.Series(_TR["TR"], name="TR")

def ATR(ohlc, period = 14,high="High",low="Low",close="Close"):
        """Average True Range is moving average of True Range."""

        mytr=TR(ohlc,high=high,low=low,close=close)
        return pd.Series(
            mytr.rolling(center=False, window=period).mean(),
            name="{0} period ATR".format(period),
        )

def DMI(ohlc, period = 14, adjust = True,high="High",low="Low",close="Close"):
        """The directional movement indicator (also known as the directional movement index - DMI) is a valuable tool
         for assessing price direction and strength. This indicator was created in 1978 by J. Welles Wilder, who also created the popular
         relative strength index. DMI tells you when to be long or short.
         It is especially useful for trend trading strategies because it differentiates between strong and weak trends,
         allowing the trader to enter only the strongest trends.
        source: https://www.tradingview.com/wiki/Directional_Movement_(DMI)#CALCULATION

        :period: Specifies the number of Periods used for DMI calculation
        """

        ohlc["up_move"] = ohlc[high].diff()
        ohlc["down_move"] = -ohlc[low].diff()

        # positive Dmi
        def _dmp(row):
            if row["up_move"] > row["down_move"] and row["up_move"] > 0:
                return row["up_move"]
            else:
                return 0

        # negative Dmi
        def _dmn(row):
            if row["down_move"] > row["up_move"] and row["down_move"] > 0:
                return row["down_move"]
            else:
                return 0

        ohlc["plus"] = ohlc.apply(_dmp, axis=1)
        ohlc["minus"] = ohlc.apply(_dmn, axis=1)

        diplus = pd.Series(
            100
            * (ohlc["plus"] / ATR(ohlc, period = period,high=high,low=low,close=close))
            .ewm(alpha=1 / period, adjust=adjust)
            .mean(),
            name="DI+",
        )
        diminus = pd.Series(
            100
            * (ohlc["minus"] / ATR(ohlc, period = period,high=high,low=low,close=close))
            .ewm(alpha=1 / period, adjust=adjust)
            .mean(),
            name="DI-",
        )

        return pd.concat([diplus, diminus], axis=1)

def ADX(ohlc, period = 14, adjust = True,high="High",low="Low",column="Close"):
        """The A.D.X. is 100 * smoothed moving average of absolute value (DMI +/-) divided by (DMI+ + DMI-). ADX does not indicate trend direction or momentum,
        only trend strength. Generally, A.D.X. readings below 20 indicate trend weakness,
        and readings above 40 indicate trend strength. An extremely strong trend is indicated by readings above 50"""

        dmi = DMI(ohlc, period = period, adjust = adjust,high=high,low=low,close=column)
        return pd.Series(
            100
            * (abs(dmi["DI+"] - dmi["DI-"]) / (dmi["DI+"] + dmi["DI-"]))
            .ewm(alpha=1 / period, adjust=adjust)
            .mean(),
            name="{0} period ADX.".format(period),
        )

plt.figure(figsize=(12,6))
dff["ADX"]=ADX(dff, period = 20, adjust = True,high="High",low="Low",column="Close")
dff['ADX'].plot(label='ADX20')
plt.title('AMD ADX')
plt.legend()
AMD ADX20 indicator

Inferences:

  • The AMD’s total return over the DAT5 period is 200%.
  • STD of daily returns is 3.3%.
  • The kurtosis and histogram of daily returns indicate that the corresponding empirical distribution is close to normal distribution (kurtosis=3.0) with short tails.
  • The positive skewness<0.5 of daily returns shows that the right-skewed distribution is almost symmetrical. The positive skewness of a distribution indicates that an investor may expect frequent small losses and a few large gains from the investment.
  • The AMD SMA50–200 crossover strategy generates a sell signal or “death cross” at the end of the DAT5 period when a 50-day moving average crosses below a 200-day moving average.
  • A zero line crossover: Since the MACD crosses below the zero line, the market is bearish and a short sell signal. However, positive MACD histogram bars indicate increasing upward momentum.
  • The 20.00 ADX level acts as a tiebreaker: if the ADX is below the 20.00 level, price is in a range or in a very weak trend and only when the ADX breaks above the 20.00 level, it signals a strong trend that is likely to continue.

Conclusions

  • This paper has addressed the intersection of Algorithmic Trading and Technical Analysis of top growth stocks.
  • While technical analysis aims to decipher market trends and patterns, algorithmic trading utilizes the speed and precision of computer algorithms to optimize trade execution.
  • Specific results of the present study can be summarized as follows:

The Sharpe Ratio of Top 7 Tech Stocks

  • AMZN, GOOG, META, and MSFT have SR<1.0. A ratio under 1.0 is considered sub-optimal.
  • AAPL and TSLA have the SR values of 0.97 and 1.04, respectively. A SR of close to 1.0 or greater is typically considered good.
  • NVDA has the SR of 1.63 that is higher than the other 6 tech stocks.
  • In risk-adjusted terms, NVDA would be the most acceptable investment opportunity.

NVDA PSR & 95% CI

  • Combinations of skewness and kurtosis in returns distribution impact the STD and the confidence bands of the SR.
  • The PSR metric accounts for the STD and uncertainty associated with estimating SR (Type I errors) [5].

NVDA Daily/Cumulative Returns

  • Cumulative returns are the total gains or losses of an investment over a specific period. It is a crucial metric that investors use to determine the overall performance of their portfolio.
  • This information can help investors adjust their portfolio to maximize their returns.
  • However, taxes can substantially reduce the cumulative returns for most investments unless they are held in tax-advantaged accounts.

NVDA Keltner Channel (KC) Strategy

  • We can see that the KC delivers powerful breakouts signals. The incorporation of ATR ensures that the KC is more effective when volatility rises [6].
  • Traders can join a bullish breakout early when there is a pullback to the outside of the upper line. Likewise, when a bearish breakout occurs, traders can seek to place aggressive sell orders when the price pulls back to the outside of the lower line [6].
  • The expected ROI of the KC trading strategy by investing $100k in NVDA is 123%.
  • Results show that the KC trading strategy has outperformed the SPY ETF by 109%.

NVDA VWAP Support & Resistance

  • A stock trading above the VWAP as the line rises indicates an uptrend and vice versa on a downtrend.
  • VWAP is a simple line that acts as a support if the stock is trading above it (cf. 2023–2024) and a resistance if the stock is trading below it (cf. 2022).

NVDA Fundamental Analysis

  • The beta is 1.68, so NVIDIA’s price volatility has been higher than the market average.
  • The Risk Score for NVIDIA Corp. is significantly higher than its peer group’s. This means that NVIDIA Corp. is significantly less risky than its peer group.
  • Conventional wisdom says that a PEG ratio of 1 or less is considered good (at par or undervalued to its growth rate). A value greater than 1, in general, is not as good (overvalued to its growth rate).
  • NVIDIA 2024 annual Diluted EPS was $1.19, a 585.63% increase from 2023.
  • NVDA Debt/Equity (D/E) Ratio is ~0.2, whereas INTC D/E ~ 0.46 (April 28, 2024).
  • NVDA Revenue Per Share (ttm) is 3.23.

Backtesting NVDA Williams %R & MACD Trading Strategy

  • Backtesting the WR-MACD strategy is important because it allows us to test this strategy on historical data (viz. DAT2) to evaluate its viability before risking capital in real-time trading.
  • However, past data isn’t necessarily a good predictor of future market behavior, so no strategy can guarantee accuracy.
  • Also, backtesting is different from scenario analysis and the forward performance approach to testing the effectiveness of a given trading strategy.

Monte Carlo Simulation of NVDA Value-at-Risk (VaR)

  • Student’s t distribution seems to fit better than the normal distribution (cf. the r.h.s. tail of the distribution).
  • The 0.05 empirical quantile of daily returns is at -0.055. That means that with 95% confidence, our worst daily loss will not exceed 5.5%. If we have a $1 M investment, our one-day 5% VaR is 0.055 * $1 M = $55 k.
  • Final price is spread out between $110 (our portfolio has lost value) to almost $134. We can see that the expectation (mean outcome) is a profit; this is due to the fact that the drift in our random walk (parameter mu) is positive.
  • Our big Monte Carlo simulation of random walks yields the probability distribution of the final price and quantile measures for the estimated VaR.
  • We have looked at the 1% empirical quantile of the final price distribution to estimate the VaR, which is $102 for a $121.255 investment.

NVDA RSI & Bollinger Bands Support-Resistance

  • The current NVDA RSI value is below 70 and well above 30, suggesting NVDA is neither overbought nor oversold (i.e. Oversold / Overbought: Neutral / Hold).
  • 20 Day Bollinger Bands also suggest Hold.
  • Widening Bands indicate an increase in price volatility, which often signals the start of a strong trend (as of 2024–08–29).

Risk-Return Comparison of 10 Stocks to Watch

  • We have implemented moving average based strategies to buy and/or sell stocks for 10 stocks of interest and compared the results with the ones obtained when using the buy and hold strategy.
  • We can see that the SMA 50–200 trading strategy performed better than the buy and hold strategy with 467% and 453% returns, respectively.
  • The Risk-Return plot shows that NVDA has max expected return and medium risk; DXCM has min expected return and medium risk; MSFT has medium expected return and min risk; TSLA has high expected return (second after NVDA) and max risk.
  • This is a part of debate that has been going on for decades. Buy and Hold vs. Timing the Market: Which one is better?

Backtest GOOG RSI-SMA Trading Strategies

  • This example shows some of the features of backtesting.py, a Python framework for backtesting trading strategies.

Backtest.run() method returns a pandas Series of simulation results and statistics associated with our strategy.

Backtest.plot() method provides the same insights in a more visual form.

  • We see that this simple strategy makes almost 84% return in the period of 2020–2024, with annual volatility 18%, max drawdown 12%, and with max drawdown period spanning 617 days.
Sortino Ratio                        1.422444
  • A good Sortino ratio typically exceeds 1. It measures the risk-adjusted returns, focusing on downside risk. A higher Sortino ratio indicates better performance in managing downside volatility, which is crucial for risk-averse investors.
Calmar Ratio                         1.178269
  • If the Calamar ratio is more than 1, it indicates that the returns are higher than the drawdown, albeit slightly.
Sharpe Ratio                         0.791473
  • Investments with less than 1.00 Sharpe Ratio do not generate high returns. Contrarily, investments with a Sharpe Ratio of 1.00 to 3.00 or above have higher returns subsequently.

AMD SMA-MACD-ADX Trend-Following Analysis:

  • The AMD’s total return over the DAT5 period is 200%.
  • STD of daily returns is 3.3%.
  • The kurtosis and histogram of daily returns indicate that the corresponding empirical distribution is close to normal distribution (kurtosis=3.0) with short tails.
  • The positive skewness<0.5 of daily returns shows that the right-skewed distribution is almost symmetrical. The positive skewness of a distribution indicates that an investor may expect frequent small losses and a few large gains from the investment.
  • The AMD SMA50–200 crossover strategy generates a sell signal or “death cross” at the end of the DAT5 period when a 50-day moving average crosses below a 200-day moving average.
  • A zero line crossover: Since the MACD crosses below the zero line, the market is bearish and a short sell signal. However, positive MACD histogram bars indicate increasing upward momentum.
  • The 20.00 ADX level acts as a tiebreaker: if the ADX is below the 20.00 level, price is in a range or in a very weak trend and only when the ADX breaks above the 20.00 level, it signals a strong trend that is likely to continue.

Bottom Line:

We have looked at the key trends, case examples and best industry practices that set to accelerate the algo trading market growth in 2024 and beyond. Factors such as the increasing adoption of real-time data analytics, interactive visualizations, and the rising demand for automated trading systems substantiate the rapid algo trading industry growth in 3 key regions such as North America, Europe, and Asia.

References

  1. Risk and Returns: The Sharpe Ratio
  2. The Sharpe Ratio Defined
  3. Algorithmic Trading with the Keltner Channel in Python
  4. rubenbriones/Probabilistic-Sharpe-Ratio/<~tree/master/src>
  5. Is Your Sharpe Ratio Lying to You? Meet the Probabilistic Sharpe Ratio
  6. Mechanics and Calculation of Keltner Channel
  7. Keltner Channel
  8. Building a VWAP Indicator in Python
  9. I Tested a VWAP Strategy on the 50 Largest S&P 500 Companies. Here Are the Results
  10. How to Pick Stocks: Fundamentals vs. Technicals
  11. Increasing Stock Returns by Combining Williams %R and MACD in Python
  12. Algorithmic Trading with Williams %R in Python
  13. Algorithmic-Trading-with-Python/Advanced Strategies/ Williams%R_MACD.py
  14. Stock market trends and Value at Risk
  15. Value at Risk (VaR) and Its Implementation in Python
  16. Monte Carlo simulation using Black-Scholes for stock price in Python
  17. Algorithmic Trading with Python: The RSI-Bollinger Band Strategy
  18. Bollinger Band and RSI Strategy: An In-depth Analysis
  19. Buy And Hold Vs SMA Crossover: Big Profits With Simple Strategy
  20. SharmaVidhiHaresh/Backtesting-Trading-Strategies-with-Python
  21. MACD Analysis Made Easy with Python
  22. NVDA Technical Analysis using 75 Simplified FinTA Indicators

Relevant 3rd Party SaaS

Macroaxis Wealth Optimization Platform

Read more: Macroaxis AI Investment Opportunity

TradingView — Track All Markets

Read more: Track All Markets with TradingView

Barchart.com | Commodity, Stock, and Currency Quotes, Charts …

See the use-case example: OXY Stock Analysis, Thursday, 23 June 2022

Explore More

Infographic

Four Pillars of Financial Risk: Market Risk, Credit Risk, Liquidity Risk, and Operational Risk
A Hybrid Trading Decision Framework: Technical Analysis, Statistical Modeling, and Machine Learning.

Read more:

3 Steps Towards max (Return/Risk) Ratio: Diversification, Automation, and Price Prediction.
NVDA Stock in Focus

NVIDIA Rolling Volatility: GARCH & XGBoost

NVIDIA Returns-Drawdowns MVA & RNN Mean Reversal Trading

Learn how to predict the stock market
Inflation-Resistant Stocks to Buy Now
AI+TTI: Automated Trading Technical Analysis
5-Step Roadmap Towards min(Risk/Return): (1) Stock Analytics, (2) max (Return), (3) min (Fees), (4) min (Risk/Return), and (5) Traffic-Light Decision.
The ultimate universal goal of a resilient investor is to minimize the Risk/Return Ratio (RRR). Risk is the difference between entry point for the trade and the stop-loss order.
min(RRR) = A Balanced Portfolio Example AAPL vs MSFT
RRR Real World Example AAPL vs MSFT
Macroaxis Risk-Return Trade-Off Explained: Daily Expected Return (%) vs Risk (%) of a Portfolio vs Market.

Read more: Invest in AI via Macroaxis Sep ’22 Update

Contacts

Disclaimer

  • The following disclaimer clarifies that the information provided in this article is for educational use only and should not be considered financial or investment advice.
  • The information provided does not take into account your individual financial situation, objectives, or risk tolerance.
  • Any investment decisions or actions you undertake are solely your responsibility.
  • You should independently evaluate the suitability of any investment based on your financial objectives, risk tolerance, and investment timeframe.
  • It is recommended to seek advice from a certified financial professional who can provide personalized guidance tailored to your specific needs.
  • The tools, data, content, and information offered are impersonal and not customized to meet the investment needs of any individual. As such, the tools, data, content, and information are provided solely for informational and educational purposes only.
Python
Algorithmic Trading
Technical Analysis
Tech
Volatility
Recommended from ReadMedium