avatarAlexzap

Summary

The provided content outlines a comprehensive backtesting analysis of an SMA (Simple Moving Average) cross strategy applied to AMD (Advanced Micro Devices) stock data, with a focus on optimizing the strategy through long/short SMA length tuning to outperform Buy & Hold returns.

Abstract

The article delves into the application of vectorized backtesting to evaluate the performance of an SMA cross strategy for AMD stock from 2019 to 2024. It emphasizes the importance of hyperparameter optimization in machine learning, akin to finding the optimal SMA lengths for trading. The study iterates over 9,165 combinations of short and long SMA lengths to identify the most profitable strategy, ultimately pinpointing the optimal SMA lengths as 47 and 86. The analysis includes descriptive statistics of AMD's stock returns, the use of technical indicators such as EMA, RSI, and Bollinger Bands, and a comparison of the SMA strategy's performance against the Buy & Hold approach. The optimal SMA strategy is further validated through deployment via Backtesting 0.3.3, demonstrating its potential for superior returns and risk management.

Opinions

  • The author believes that AMD is a prime candidate for algorithmic trading strategies due to its strong performance metrics and growth in the semiconductor industry.
  • There is an opinion that automated optimization of trading strategies, specifically SMA cross strategies, can lead to better returns than passive investment strategies like Buy & Hold.
  • The article suggests that the use of vectorized backtesting is an efficient method for testing trading strategies across a wide range of parameters.
  • The author conveys that the optimal SMA strategy, once deployed, can yield significant returns, as evidenced by the backtesting results.
  • There is a cautious optimism regarding the use of backtesting, with a reminder that it should be complemented by other risk management strategies and a thorough understanding of market dynamics before live trading.
  • The article implies that technical indicators, when combined and properly tuned, can provide valuable insights for trading decisions.

Backtesting AMD Algo-Trading SMA Cross Strategy: Long/Short Auto Tuning vs Buy&Hold

5 Main Categories of Technical Indicators
  • The present study aims at vectorized backtesting AMD algo-trading Simple Moving Average (SMA) cross strategy 2019–2024.
  • Here, the main focus is on long/short length auto tuning to maximize expected SMA strategy returns while outperforming the passive Buy&Hold returns.
  • As with hyperparameter optimization in ML, we will iterate over various combinations of SMA lengths to find the optimal (hyper) parameters that maximize the annual returns/risk ratio.
  • In a nutshell, this paper is about automated optimization of the SMA cross strategy under the umbrella of vectorized backtesting.
  • Why AMD: AMD is beating Intel on all the metrics that matter; AMD is identified as world’s fastest-growing semiconductor brand; AMD continues its leadership growth in HPC.
  • The key steps of our approach are as follows:
  1. Reading AMD time series data with yfinance
  2. Plotting the Plotly Candlesticks with/without Indicators
  3. Descriptive statistics (kurtosis, skewness, std, etc.) of daily returns
  4. Examining the Default SMA 55/200 Cross Strategy
  5. Finding the Optimal SMA (47/86) Cross Strategy by iterating over 9165 combinations of Short/Long lengths
  6. Defining position and calculating strategy returns
  7. Comparing absolute performance and annual returns/risk: SMA strategy vs Buy&Hold
  8. Calculating and visualizing cumulative returns of SMA vs benchmark
  9. Optimal SMA Strategy Deployment via Backtesting 0.3.3

Introduction to the SMA Indicator

  • The SMA is an arithmetic moving average calculated by adding recent prices and then dividing that figure by the number of time periods in the calculation average.
  • The SMA is a trend indicator that smooths price movements to filter out the noise of an asset. Traders frequently use SMA to open/close trades through SMA crossovers, and to find supports and resistances in different time frames. These crossovers are generated by predefining two moving averages, a slow and a fast one. The slow SMA takes into account a larger amount of periods, then catching the general trend of the asset. And the fast SMA is calculated with fewer periods, reacting quicker to price movements.
  • During a bearish trend, when the fast SMA crosses the slow one upward, the trend is likely to have a reversal and the BUY signal. However, in a bullish trend and the fast SMA crosses the slow one downward, we will get the SELL signal.

Basic Setup & Input Stock Data

  • Setting the working directory YOURPATH
import os
os.chdir('YOURPATH')    # Set working directory
os. getcwd() 
  • Importing the necessary libraries
import datetime
import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
import seaborn as sns
import plotly.graph_objects as go
sns.set_style("whitegrid")
from itertools import product

import yfinance as yf

# ignore the warnings
import warnings
warnings.filterwarnings('ignore')
  • Reading the input historical stock data using get_data
#daily data
def get_data(pairs):
    global df, symbol
    NUM_DAYS = 2000    # The number of days of historical data to retrieve

    #define start & dates
    start = (datetime.date.today() - datetime.timedelta( NUM_DAYS ) )
    end = datetime.datetime.today()
    
    #pull data
    df = yf.download(pairs, start=start)
    return df

pair = "AMD"
df = get_data(pair)
df.tail()
AMD historical stock data
  • Printing the descriptive summary statistics of Close price
df['Close'].describe().T

count    1081.000000
mean       98.584190
std        32.889866
min        38.709999
25%        78.339996
50%        92.309998
75%       113.180000
max       211.380005
Name: Close, dtype: float64

Plotting the Candlestick Chart

  • Plotting the AMD candlestick chart without indicators
# Plotting candlestick chart without indicators
fig = make_subplots(rows=2, cols=1, shared_xaxes=True, vertical_spacing=0.05, row_heights = [0.7, 0.3])
fig.add_trace(go.Candlestick(x=df.index,
                             open=df['Open'],
                             high=df['High'],
                             low=df['Low'],
                             close=df['Adj Close'],
                             name='AMD'),
              row=1, col=1)


# Plotting volume chart on the second row 
fig.add_trace(go.Bar(x=df.index,
                     y=df['Volume'],
                     name='Volume',
                     marker=dict(color='black', opacity=1.0)),
              row=2, col=1)

# Plotting annotation
fig.add_annotation(text='AMD',
                    font=dict(color='white', size=40),
                    xref='paper', yref='paper',
                    x=0.5, y=0.65,
                    showarrow=False,
                    opacity=0.2)

# Configuring layout
fig.update_layout(title='c',
                  yaxis=dict(title='Price (USD)'),
                  height=1000,
                 template = 'plotly_white')

# Configuring axes and subplots
fig.update_xaxes(rangeslider_visible=False, row=1, col=1)
fig.update_xaxes(rangeslider_visible=False, row=2, col=1)
fig.update_yaxes(title_text='Price (USD)', row=1, col=1)
fig.update_yaxes(title_text='Volume', row=2, col=1)

fig.show()
AMD candlestick chart vs volume

Plotting the Candlestick Chart with Indicators

  • Plotting the AMD candlestick chart with indicators
# Adding Moving Averages
df['EMA9'] = df['Adj Close'].ewm(span = 9, adjust = False).mean() # Exponential 9-Period Moving Average
df['SMA20'] = df['Adj Close'].rolling(window=20).mean() # Simple 20-Period Moving Average
df['SMA50'] = df['Adj Close'].rolling(window=50).mean() # Simple 50-Period Moving Average
df['SMA100'] = df['Adj Close'].rolling(window=100).mean() # Simple 100-Period Moving Average
df['SMA200'] = df['Adj Close'].rolling(window=200).mean() # Simple 200-Period Moving Average

# Adding RSI for 14-periods 
delta = df['Adj Close'].diff() # Calculating delta
gain = delta.where(delta > 0,0) # Obtaining gain values
loss = -delta.where(delta < 0,0) # Obtaining loss values
avg_gain = gain.rolling(window=14).mean() # Measuring the 14-period average gain value
avg_loss = loss.rolling(window=14).mean() # Measuring the 14-period average loss value
rs = avg_gain/avg_loss # Calculating the RS
df['RSI'] = 100 - (100 / (1 + rs)) # Creating an RSI column to the Data Frame 

# Adding Bollinger Bands 20-periods
df['BB_UPPER'] =df['SMA20'] + 2*df['Adj Close'].rolling(window=20).std() # Upper Band
df['BB_LOWER'] = df['SMA20'] - 2*df['Adj Close'].rolling(window=20).std() # Lower Band

# Adding ATR 14-periods
df['TR'] = pd.DataFrame(np.maximum(np.maximum(df['High'] - df['Low'], abs(df['High'] - df['Adj Close'].shift())), abs(df['Low'] - df['Adj Close'].shift())), index = df.index)
df['ATR'] = df['TR'].rolling(window = 14).mean() # Creating an ART column to the Data Frame 
  • Creating plotly visuals of AMD candlesticks with 8 indicators vs volume
# Plotting Candlestick charts with indicators
fig = make_subplots(rows=4, cols=1, shared_xaxes=True, vertical_spacing=0.05,row_heights=[0.6, 0.10, 0.10, 0.20])

# Candlestick 
fig.add_trace(go.Candlestick(x=df.index,
                             open=df['Open'],
                             high=df['High'],
                             low=df['Low'],
                             close=df['Adj Close'],
                             name='AMD'),
              row=1, col=1)

# Moving Averages
fig.add_trace(go.Scatter(x=df.index,
                         y=df['EMA9'],
                         mode='lines',
                         line=dict(color='#90EE90'),
                         name='EMA9'),
              row=1, col=1)

fig.add_trace(go.Scatter(x=df.index,
                         y=df['SMA20'],
                         mode='lines',
                         line=dict(color='yellow'),
                         name='SMA20'),
              row=1, col=1)

fig.add_trace(go.Scatter(x=df.index,
                         y=df['SMA50'],
                         mode='lines',
                         line=dict(color='orange'),
                         name='SMA50'),
              row=1, col=1)

fig.add_trace(go.Scatter(x=df.index,
                         y=df['SMA100'],
                         mode='lines',
                         line=dict(color='purple'),
                         name='SMA100'),
              row=1, col=1)

fig.add_trace(go.Scatter(x=df.index,
                         y=df['SMA200'],
                         mode='lines',
                         line=dict(color='red'),
                         name='SMA200'),
              row=1, col=1)

# Bollinger Bands
fig.add_trace(go.Scatter(x=df.index,
                         y=df['BB_UPPER'],
                         mode='lines',
                         line=dict(color='#00BFFF'),
                         name='Upper Band'),
              row=1, col=1)

fig.add_trace(go.Scatter(x=df.index,
                         y=df['BB_LOWER'],
                         mode='lines',
                         line=dict(color='#00BFFF'),
                         name='Lower Band'),
              row=1, col=1)

fig.add_annotation(text='AMD',
                    font=dict(color='white', size=40),
                    xref='paper', yref='paper',
                    x=0.5, y=0.65,
                    showarrow=False,
                    opacity=0.2)


# Relative Strengh Index (RSI)
fig.add_trace(go.Scatter(x=df.index,
                         y=df['RSI'],
                         mode='lines',
                         line=dict(color='#CBC3E3'),
                         name='RSI'),
              row=2, col=1)

# Adding marking lines at 70 and 30 levels
fig.add_shape(type="line",
              x0=df.index[0], y0=70, x1=df.index[-1], y1=70,
              line=dict(color="red", width=2, dash="dot"),
              row=2, col=1)
fig.add_shape(type="line",
              x0=df.index[0], y0=30, x1=df.index[-1], y1=30,
              line=dict(color="#90EE90", width=2, dash="dot"),
              row=2, col=1)

# Average True Range (ATR)
fig.add_trace(go.Scatter(x=df.index,
                         y=df['ATR'],
                         mode='lines',
                         line=dict(color='#00BFFF'),
                         name='ATR'),
              row=3, col=1)


# Volume
fig.add_trace(go.Bar(x=df.index,
                     y=df['Volume'],
                     name='Volume',
                     marker=dict(color='orange', opacity=1.0)),
              row=4, col=1)


# Layout
fig.update_layout(title='AMD Candlestick Chart & Indicators',
                  yaxis=dict(title='Price (USD)'),
                  height=1000,
                 template = 'plotly_dark')

# Axes and subplots
fig.update_xaxes(rangeslider_visible=False, row=1, col=1)
fig.update_xaxes(rangeslider_visible=False, row=4, col=1)
fig.update_yaxes(title_text='Price (USD)', row=1, col=1)
fig.update_yaxes(title_text='RSI', row=2, col=1)
fig.update_yaxes(title_text='ATR', row=3, col=1)
fig.update_yaxes(title_text='Volume', row=4, col=1)
fig.show()
AMD candlesticks with 8 indicators vs volume

Analysis of Daily & Cumulative Returns

  • Computing the AMD daily returns and plotting their histogram
df['Daily-Return']=df['Close'].pct_change(1)
df['Daily-Return'].plot(kind='hist', subplots=True, title=['AMD Daily Return'])
AMD Daily Return
  • Computing and plotting the AMD cumulative return
df['cum_ret'] = (1 + df['Daily-Return']).cumprod() - 1 
df['cum_ret'].plot(subplots=True, title=['AMD Cumulative Return'])
AMD Cumulative Return

Default SMA 55/200 Cross Strategy

  • Calculating and plotting SMA 55/200 rolling means
#close price only
data = df.Close.to_frame()
#define SMA values

sma_short = 55
sma_long = 200

#calculate SMA values 
data["SMA_55"] = data.Close.rolling(sma_short).mean()
data["SMA_200"] = data.Close.rolling(sma_long).mean()


#drop missing values
data = data.dropna()
data.plot(figsize = (12,7), title = f"{pair} + SMA{sma_short} & SMA{sma_long}")
AMD Close price vs SMA 55/200
  • Defining position and calculating returns vs annual risk
# Define Positions (Long or Short). Where 1 = Buy, -1 = Sell

data["position"] = np.where(data["SMA_55"] > data["SMA_200"], 1, -1)
data["returns"] = np.log(data.Close.div(data.Close.shift(1)))


#daily return of strategy
data["strategy"] = data.position.shift(1) * data["returns"]
data.dropna(inplace = True)

#calculate absolute performance

data[["returns", "strategy"]].sum()
returns     0.698933
strategy    0.443787
dtype: float64

#CALCULATE ACTUAL VALUE OF ABSOLUTE PERFORMANCE

data[["returns", "strategy"]].sum().apply(np.exp)
returns     2.011605
strategy    1.558598
dtype: float64

#annual risk
data[["returns", "strategy"]].std() * np.sqrt(252)
returns     0.503509
strategy    0.503603
dtype: float64

data["cum_returns"] = data["returns"].cumsum().apply(np.exp)
data["cum_strategy"] = data["strategy"].cumsum().apply(np.exp)
data
Position & returns
  • Plotting cumulative returns of the SMA strategy vs Buy&Hold
#Visualize Cummulative returns
data[["cum_returns", "cum_strategy"]].plot(figsize = (12,7), title = f"{pair} Returns + SMA{sma_short} & SMA{sma_long}" )
plt.legend(fontsize = 12)
AMD SMA strategy vs Buy&Hold
  • Calculating the strategy performance
strat_perf = data.cum_strategy.iloc[-1] - data.cum_returns.iloc[-1]
round(strat_perf,5)
-1.22271

SMA Backtesting with Long/Short Optimization

#Create Backtest function

def sma_backtest(data, sma_short, sma_long):
    df = data.copy()
    df["returns"] = np.log(df.Close.div(df.Close.shift(1)))
    df["SMA_55"] = data.Close.rolling(sma_short).mean()
    df["SMA_200"] = data.Close.rolling(sma_long).mean()
    df.dropna(inplace = True)
    
    df["position"] = np.where(df["SMA_55"] > df["SMA_200"], 1, -1)
    df["strategy"] = df.position.shift(1) * df["returns"]
    df.dropna(inplace = True)
    
    return round(np.exp(df["strategy"].sum()), 5) #returns the absolute performance of strategy
  • Running the 2 test examples
abs_perf = sma_backtest(data, 55, 200)
abs_perf
2.25107

abs_perf = sma_backtest(data, 75, 150)
abs_perf
1.61616
  • Iterating through sma_short = and sma_long ranges [3,50] and [55,250], respectively, viz.
#range of sma
range_sma_short = range(3, 50, 1)
range_sma_long = range(55, 250, 1)
#combination of SMAs
smas = list(product(range_sma_short, range_sma_long))
smas
[(3, 55),
 (3, 56),
 (3, 57),
.........
 (8, 76),
 (8, 77),
 (8, 78),
 (8, 79),
 ...]

#iterate through sma combination and test strategy 
abs_perform = []
for pro in smas:
    abs_perform.append(sma_backtest(data, pro[0], pro[1]))

#get max performance value
np.max(abs_perform)
6.8029

#get max value combination
best_comb = smas[np.argmax(abs_perform)]
best_comb
(47, 86)
  • Creating the summary performance DataFrame
#results to dataframe
results = pd.DataFrame(smas, columns = ["SMA_short", "SMA_long"])
results
SMA_short SMA_long
0 3 55
1 3 56
2 3 57
3 3 58
4 3 59
... ... ...
9160 49 245
9161 49 246
9162 49 247
9163 49 248
9164 49 249
9165 rows × 2 columns

results["abs_performance"] = abs_perform
results
SMA_short SMA_long abs_performance
0 3 55 1.86706
1 3 56 1.49948
2 3 57 1.20392
3 3 58 1.25719
4 3 59 1.34725
... ... ... ...
9160 49 245 1.35310
9161 49 246 1.33530
9162 49 247 1.31788
9163 49 248 1.25268
9164 49 249 1.25448
9165 rows × 3 columns

#top 10 best performing combination
best = results.nlargest(10, "abs_performance")
best
SMA_short SMA_long abs_performance
8611 47 86 6.80290
9002 49 87 6.44140
7489 41 134 6.39373
8416 46 86 6.23989
8417 46 87 6.13969
7487 41 132 6.03575
7681 42 131 5.98745
7488 41 133 5.97510
7682 42 132 5.96143
7294 40 134 5.96075

#top 10 least performing combination
least = results.nsmallest(10, "abs_performance")
least

SMA_short SMA_long abs_performance
4886 28 66 0.68864
5469 31 64 0.69114
5082 29 67 0.70002
5275 30 65 0.71371
159 3 214 0.72108
4690 27 65 0.72177
394 5 59 0.73426
4124 24 84 0.74273
5274 30 64 0.74340
5079 29 64 0.75038

Optimal SMA 47/86 Cross Strategy

  • Defining the best performing SMA strategy
#define SMA values

sma_short = best_comb[0]
sma_long = best_comb[1]

#calculate SMA values 
new_data = data.Close.to_frame()
new_data["SMA_s"] = data.Close.rolling(sma_short).mean()
new_data["SMA_l"] = data.Close.rolling(sma_long).mean()


#drop missing values
new_data = new_data.dropna()
new_data

Close SMA_s SMA_l
Date   
2020-01-27 49.259998 44.387660 38.855000
2020-01-28 50.529999 44.614255 39.099302
2020-01-29 47.509998 44.746596 39.308256
2020-01-30 48.779999 44.912553 39.532791
2020-01-31 47.000000 45.071702 39.745349
... ... ... ...
2024-05-20 166.330002 164.965531 172.163255
2024-05-21 164.660004 164.488936 172.186395
2024-05-22 165.520004 163.945532 172.085116
2024-05-23 160.429993 163.302553 171.994999
2024-05-24 166.360001 162.982127 171.971046
1091 rows × 3 columns
  • Plotting SMA47 vs SMA86
new_data.plot(figsize = (12,7), title = f"{pair} + SMA{sma_short} & SMA{sma_long}")
AMD Close & SMA 47/86
  • Defining position and calculating strategy returns
# Define Positions (Long or Short). Where 1 = Buy, -1 = Sell

new_data["position"] = np.where(new_data["SMA_s"] > new_data["SMA_l"], 1, -1)

new_data["returns"] = np.log(new_data.Close.div(new_data.Close.shift(1)))


#daily return of strategy
new_data["strategy"] = new_data.position.shift(1) * new_data["returns"]
new_data.dropna(inplace = True)

  • Comparing absolute performance and annual returns/risk
#calculate absolute performance
new_data[["returns", "strategy"]].sum()

returns     1.217042
strategy    1.917348
dtype: float64

#CALCULATE ACTUAL VALUE OF ABSOLUTE PERFORMANCE

new_data[["returns", "strategy"]].sum().apply(np.exp)

returns     3.377182
strategy    6.802895
dtype: float64

#annual return
new_data[["returns", "strategy"]].mean() * 252
returns     0.281371
strategy    0.443277
dtype: float64

#annual risk
new_data[["returns", "strategy"]].std() * np.sqrt(252)

returns     0.534979
strategy    0.534543
dtype: float64
  • Calculating and visualizing cumulative returns
new_data["cum_returns"] = new_data["returns"].cumsum().apply(np.exp)
new_data["cum_strategy"] = new_data["strategy"].cumsum().apply(np.exp)
new_data

           Close     SMA_s     SMA_l position returns strategy cum_returns cum_strategy
Date        
2020-01-28 50.529999 44.614255 39.099302 1 0.025455 0.025455 1.025782 1.025782
2020-01-29 47.509998 44.746596 39.308256 1 -0.061627 -0.061627 0.964474 0.964474
2020-01-30 48.779999 44.912553 39.532791 1 0.026380 0.026380 0.990256 0.990256
2020-01-31 47.000000 45.071702 39.745349 1 -0.037173 -0.037173 0.954121 0.954121
2020-02-03 48.020000 45.260425 39.966628 1 0.021470 0.021470 0.974827 0.974827

#Visualize Cummulative returns
new_data[["cum_returns", "cum_strategy"]].plot(figsize = (12,7), title = f"{pair} Returns + SMA{sma_short} & SMA{sma_long}" )
plt.legend(fontsize = 12)
plt.show
AMD Optimal SMA 47/86 strategy vs Buy&Hold

Optimal SMA Strategy Deployment via Backtesting 0.3.3

!pip install Backtesting
from backtesting import Backtest, Strategy
from backtesting.lib import crossover

from backtesting.test import SMA

he = yf.download("AMD", start="2019-05-28", interval="1d")[
    ["Open", "High", "Low", "Close", "Volume"]
]
he.tail()

           Open       High       Low        Close      Volume
Date     
2024-05-21 164.000000 165.830002 163.100006 164.660004 30005500
2024-05-22 167.410004 169.809998 163.860001 165.520004 47426700
2024-05-23 170.179993 173.139999 158.270004 160.429993 91888800
2024-05-24 161.410004 167.660004 160.250000 166.360001 54795400
2024-05-28 169.419998 174.550003 164.960007 171.610001 66395000


class SmaCross(Strategy):
    n1 = 47
    n2 = 86

    def init(self):
        close = self.data.Close
        self.sma1 = self.I(SMA, close, self.n1)
        self.sma2 = self.I(SMA, close, self.n2)

    def next(self):
        if crossover(self.sma1, self.sma2):
            self.buy()
        elif crossover(self.sma2, self.sma1):
            self.sell()

bt = Backtest(he, SmaCross, cash=10000, commission=0.002)
stats = bt.run()
bt.plot()
stats

Start                     2019-05-28 00:00:00
End                       2024-05-28 00:00:00
Duration                   1827 days 00:00:00
Exposure Time [%]                   90.396825
Equity Final [$]                 43382.942604
Equity Peak [$]                  54124.704322
Return [%]                         333.829426
Buy & Hold Return [%]              490.740121
Return (Ann.) [%]                   34.110815
Volatility (Ann.) [%]               72.011331
Sharpe Ratio                         0.473687
Sortino Ratio                        1.026182
Calmar Ratio                         0.521318
Max. Drawdown [%]                   -65.43191
Avg. Drawdown [%]                   -9.028893
Max. Drawdown Duration      780 days 00:00:00
Avg. Drawdown Duration       52 days 00:00:00
# Trades                                    1
Win Rate [%]                            100.0
Best Trade [%]                     334.211198
Worst Trade [%]                    334.211198
Avg. Trade [%]                     334.211198
Max. Trade Duration        1656 days 00:00:00
Avg. Trade Duration        1656 days 00:00:00
Profit Factor                             NaN
Expectancy [%]                     334.211198
SQN                                       NaN
_strategy                            SmaCross
_equity_curve                             ...
_trades                      Size  EntryBa...
dtype: object
Optimal SMA 47/86 Cross Strategy via Backtesting

Conclusions

  • We have implemented and tested the automated backtesting procedure of applying the SMA trading strategy to historical AMD data to determine its efficacy and to optimize its performance. It is a way to test a strategy without risking real capital.
  • The idea is that a trading strategy that did well in the past will produce a better result in the future.
  • Our backtesting is associated with an automated trading system or a bot. This is a piece of software that does hands-off analysis and then executes trades. It can also set a stop-loss and a take-profit to properly manage risks.
  • The near-term project will involve vectorized backtesting of multiple trading indicators with (hyper) parameter optimization beyond the simple SMA strategy.
  • Backtesting trading strategies will also be used to test different strategies and determine which ones are more profitable.
  • Disclaimer: Backtesting should be used in conjunction with other risk management strategies, fundamental analysis, and a deep understanding of market dynamics before deploying capital in real-world trading.

Explore More

References

Contacts

Python
Algorithmic Trading
Amd
Trading System
Automation
Recommended from ReadMedium
avatarDave Karpinsky, PhD, MBA
Best MACD Trading Strategy with over 85% Win Rate

4 min read