Welcome to an in-depth guide on using Python for algorithmic trading! In this tutorial, we’ll explore how to merge the Relative Strength Index (RSI) with support and resistance levels, creating refined signals and precise trade entries. By the end, you’ll not only grasp how these technical indicators work together but also know how to implement and visualize your strategies in Python using historical data.
What You Will Gain
- Identifying Key Support and Resistance Levels: Learn the nuances of detecting significant price thresholds.
- Trend Analysis with RSI: Understand how RSI reflects price momentum and trends.
- Integrating RSI with Support and Resistance: Leverage both indicators for synchronized signals.
- Building Custom Indicators in Python: Expand on standard methods for tailored analysis.
- Visualizing Strategy Outcomes: Use Python to analyze and present results clearly.
The Python Jupyter notebook file is available for download here
The csv historical data EURUSD used in this tutorial is available here
Before we begin, I extend an invitation for you to join my dividend investing community. By joining, you won’t miss any crucial articles and can enhance your skills as an investor. As a bonus, you’ll receive a complimentary welcome gift: A 2024 Model Book With The Newest Trading Strategies (both Options and Dividends)
Step 1: Preparing the Workspace with Libraries and Data
We begin by importing essential libraries and loading the dataset for analysis.
Code Example:
import pandas as pdimport pandas_ta as ta
# Load hourly candlestick data for EUR/USD currency pair
df = pd.read_csv("EURUSD_Candlestick_1_Hour_BID_04.05.2003-15.04.2023.csv")
# Exclude rows with zero volume and reset the index
df = df[df['volume'] != 0].reset_index(drop=True)
# Check for missing values in the dataset
df.isna().sum()
# Calculate the Relative Strength Index (RSI) with a 12-period length
df['RSI'] = ta.rsi(df['close'], length=12)
# Preview the processed data
df.tail()
Explanation:
- Library Imports:
- pandas as pd: Used for manipulating and processing structured data efficiently.
- pandas_ta as ta: Simplifies computation of technical analysis indicators such as RSI.
2. Loading the Dataset:
- The file contains hourly candlestick data for EUR/USD with fields like open, high, low, close, and volume.
3. Data Cleaning:
- Volume Check: Removing rows with zero volume ensures data reliability.
- Resetting Index: Makes the dataset orderly and continuous post-filtering.
4. Null Value Assessment:
- Identifying and handling NA values is crucial to avoid errors during computations.
5. RSI Calculation:
- RSI is computed with a 12-period length. It acts as a momentum oscillator, measuring the speed and magnitude of price movements.
6. Data Inspection:
- The tail() function confirms proper RSI addition, ensuring readiness for further steps.
Step 2: Pinpointing Support and Resistance Levels
Support and resistance zones indicate pivotal market price levels. Below are the functions to identify these zones using historical candlestick data.
Code Example:
pwick_threshold = 0.0001def support(df1, l, n1, n2):
if (df1.low[l-n1:l].min() < df1.low[l] or
df1.low[l+1:l+n2+1].min() < df1.low[l]):
return 0
candle_body = abs(df1.open[l] - df1.close[l])
lower_wick = min(df1.open[l], df1.close[l]) - df1.low[l]
if lower_wick > candle_body and lower_wick > wick_threshold:
return 1
return 0
def resistance(df1, l, n1, n2):
if (df1.high[l-n1:l].max() > df1.high[l] or
df1.high[l+1:l+n2+1].max() > df1.high[l]):
return 0
candle_body = abs(df1.open[l] - df1.close[l])
upper_wick = df1.high[l] - max(df1.open[l], df1.close[l])
if upper_wick > candle_body and upper_wick > wick_threshold:
return 1
return 0
Detailed Explanation:
- Defining Wick Threshold:
- wick_threshold = 0.0001: A small filter ensures only significant wicks (sharp price movements) are considered.
2. Support Function (support):
- Purpose: Identifies lower price zones where buying interest strengthens.
- Logic:
- If a candle’s low is higher than lows from neighboring candles, it fails the support test.
- Verifies wick size: Large lower wicks and smaller bodies often signify rejection of lower prices.
- Return Values:
- 1: Confirmed support.
- 0: No support.
3. Resistance Function (resistance):
- Purpose: Detects upper price zones where selling pressure rises.
- Logic:
- A candle’s high should exceed surrounding candles.
- Requires large upper wicks alongside small candle bodies.
- Return Values:
- 1: Confirmed resistance.
- 0: No resistance.
Step 3: Assessing Proximity to Key Levels
This section determines how close the current price is to pre-identified support or resistance levels.
Code Example:
def closeResistance(l, levels, lim, df):if len(levels) == 0:
return 0
nearest_level = min(levels, key=lambda x: abs(x - df.high[l]))
c1 = abs(df.high[l] - nearest_level) <= lim
c2 = abs(max(df.open[l], df.close[l]) - nearest_level) <= lim
c3 = min(df.open[l], df.close[l]) < nearest_level
c4 = df.low[l] < nearest_level
if (c1 or c2) and c3 and c4:
return nearest_level
return 0
def closeSupport(l, levels, lim, df):
if len(levels) == 0:
return 0
nearest_level = min(levels, key=lambda x: abs(x - df.low[l]))
c1 = abs(df.low[l] - nearest_level) <= lim
c2 = abs(min(df.open[l], df.close[l]) - nearest_level) <= lim
c3 = max(df.open[l], df.close[l]) > nearest_level
c4 = df.high[l] > nearest_level
if (c1 or c2) and c3 and c4:
return nearest_level
return 0
Step-by-Step Analysis:
- Common Inputs:
- l: Candle index for analysis.
- levels: List of known support/resistance levels.
- lim: Defines proximity bounds for closeness checks.
- df: DataFrame of candlestick data.
2. Close Resistance Function (closeResistance):
- Evaluates the high price of a candle relative to nearby resistance levels.
- Conditions ensure the price is close and underneath the level.
3. Close Support Function (closeSupport):
- Similar to closeResistance but evaluates the candle’s low and proximity to support levels.
4. Outputs:
- Returns the nearest level if conditions are met, or 0 otherwise.
Step 4: Confirming Trends Relative to Key Levels
This step involves verifying price trends in relation to the calculated support and resistance levels. The goal is to determine whether prices are respecting or breaking through these pivotal zones.
Code Example:
def confirmTrend(l, df, r_level, s_level):if r_level:
if df.low[l] > r_level:
return "above_resistance"
if df.high[l] < r_level:
return "below_resistance"
if s_level:
if df.high[l] < s_level:
return "below_support"
if df.low[l] > s_level:
return "above_support"
return "within_range"
Detailed Explanation:
- Inputs:
- l: Candle index for analysis.
- df: DataFrame containing the market data.
- r_level: Nearest resistance level.
- s_level: Nearest support level.
2. Logic Flow:
- If a resistance level exists (r_level), the function:
- Checks if the price’s low is higher than the resistance (price is above resistance).
- Verifies if the price’s high is lower than the resistance (price is below resistance).
- Similarly, for support levels (s_level):
- Checks if the high is lower than the support level (price is below support).
- Ensures the low is above support (price is above support).
- If none of the conditions are met, the price is determined to be “within range.”
3. Outputs:
- Returns one of these values: "above_resistance", "below_resistance", "above_support", "below_support", or "within_range".
This step helps confirm how the price interacts with crucial market zones, aiding in assessing potential breakouts or reversals.
Step 5: Combining RSI with Trend Confirmation for Trading Signals
Once support/resistance trends are validated, the next step is to incorporate RSI to fine-tune trading signals. A unified approach helps identify optimal buy/sell moments.
Code Example:
def generateSignal(l, df, rsi_lower, rsi_upper, r_level, s_level):trend = confirmTrend(l, df, r_level, s_level)
rsi_value = df['RSI'][l]
if trend == "below_support" and rsi_value < rsi_lower:
return "buy"
if trend == "above_resistance" and rsi_value > rsi_upper:
return "sell"
return "hold"
Detailed Explanation:
- Inputs:
- l: Candle index for analysis.
- df: DataFrame containing RSI and market data.
- rsi_lower: RSI threshold for oversold conditions (default often set around 30).
- rsi_upper: RSI threshold for overbought conditions (default often set around 70).
- r_level: Resistance level.
- s_level: Support level.
2. Logic Flow:
- Determines the trend using the confirmTrend() function.
- Checks the current RSI value for overbought or oversold conditions:
- If the price is below support and RSI indicates oversold, the signal is "buy".
- If the price is above resistance and RSI shows overbought, the signal is "sell".
- Otherwise, the signal remains "hold".
3. Outputs:
- Returns one of three trading signals:
- "buy": Suggests entering a long position.
- "sell": Suggests entering a short position.
- "hold": Advises waiting for clearer opportunities.
Step 6: Applying the Trading Strategy
Apply the support and resistance detection framework to identify actionable trading signals.
Code Implementation:
from tqdm import tqdmn1, n2, backCandles = 8, 6, 140
signal = [0] * len(df)
for row in tqdm(range(backCandles + n1, len(df) - n2)):
signal[row] = check_candle_signal(row, n1, n2, backCandles, df)
df["signal"] = signal
Explanation:
- Key Parameters:
- n1 = 8, n2 = 6: Reference candles before and after each potential support/resistance point.
- backCandles = 140: History used for analysis.
2. Signal Initialization:
- signal = [0] * len(df): Prepare for tracking identified trading signals.
3. Using tqdm Loop:
- Iterates across viable rows while displaying progress for large datasets.
4. Call to Detection Logic:
- The check_candle_signal integrates RSI dynamics and proximity validation.
5. Updating Signals in Data:
- Add results into a signal column for post-processing.
Step 7: Charting Signals with Point Annotations
Visualize market movements by mapping precise trading actions directly onto price charts.
Code Implementation:
import numpy as npdef pointpos(x):
if x['signal'] == 1:
return x['high'] + 0.0001
elif x['signal'] == 2:
return x['low'] - 0.0001
else:
return np.nan
df['pointpos'] = df.apply(lambda row: pointpos(row), axis=1)
Breakdown:
- Logic Behind pointpos:
- Ensures buy signals (1) sit slightly above high prices.
- Ensures sell signals (2) sit slightly below low prices.
- Returns NaN if signals are absent.
2. Dynamic Point Generation:
- Applies point positions across rows, overlaying signals in visualizations.
Step 8: Signal Visualization on Candlestick Chart
Create comprehensive overlays of detected signals atop candlestick plots for better interpretability.
Code Implementation:
import plotly.graph_objects as godfpl = df[100:300] # Focused segment
fig = go.Figure(data=[go.Candlestick(x=dfpl.index,
open=dfpl['open'],
high=dfpl['high'],
low=dfpl['low'],
close=dfpl['close'])])
fig.add_scatter(x=dfpl.index, y=dfpl['pointpos'],
mode='markers', marker=dict(size=8, color='MediumPurple'))
fig.update_layout(width=1000, height=800, paper_bgcolor='black', plot_bgcolor='black')
fig.show()
Insight:
- Combines candlestick data with signal scatter annotations.
- Facilitates immediate recognition of actionable zones.
Step 9: Refining with Close-Level Plots
Enrich visual plots with horizontal demarcations for enhanced contextuality.
Code Implementation:
from plotly.subplots import make_subplots# Extended check
fig.add_shape(type="line", x0=10, ...) # Stub logic for signal-resistance pair representation
Step 9: Visualizing Support and Resistance Levels
Enhancing the strategy further, we visualize the detected support and resistance levels alongside the trading signals on the price chart.
Code Implementation:
def plot_support_resistance(df, backCandles, proximity):import plotly.graph_objects as go
# Extract a segment of the DataFrame for visualization
df_plot = df[-backCandles:]
fig = go.Figure(data=[go.Candlestick(
x=df_plot.index,
open=df_plot['open'],
high=df_plot['high'],
low=df_plot['low'],
close=df_plot['close']
)])
# Add detected support levels as horizontal lines
for i, level in enumerate(df_plot['support'].dropna().unique()):
fig.add_hline(y=level, line=dict(color="MediumPurple", dash='dash'), name=f"Support {i}")
# Add detected resistance levels as horizontal lines
for i, level in enumerate(df_plot['resistance'].dropna().unique()):
fig.add_hline(y=level, line=dict(color="Crimson", dash='dash'), name=f"Resistance {i}")
fig.update_layout(
title="Support and Resistance Levels with Price Action",
autosize=True,
width=1000,
height=800,
)
fig.show()
Highlights:
- Horizontal Support & Resistance Lines:
- support levels are displayed in purple dashes for clarity.
- resistance levels use red dashes to signify obstacles above the price.
2. Candlestick Chart:
- Depicts open, high, low, and close prices for each candle.
3. Dynamic Updates:
- Automatically adjusts based on selected data ranges (backCandles).
Step 10: Backtesting the Strategy
Before deploying, it’s crucial to assess the strategy’s historical performance by backtesting its rules.
Code Implementation:
def backtest_strategy(df, initial_balance=10000, lot_size=1):balance = initial_balance
position = None
for index, row in df.iterrows():
# Buy signal
if row['signal'] == 1 and position is None:
entry_price = row['close']
position = 'long'
print(f"Bought at {entry_price} on {index}")
# Sell signal
elif row['signal'] == 2 and position == 'long':
exit_price = row['close']
balance += (exit_price - entry_price) * lot_size
print(f"Sold at {exit_price} on {index}, New Balance: {balance}")
position = None
print(f"Final Balance: {balance}")
return balance
Key Notes:
- Initial Conditions:
- Begins with a fixed balance of $10,000 and a basic lot size.
2. Trade Execution Logic:
- Triggers a buy on bullish signals and sell on bearish signals, adjusting the balance accordingly.
3. Profit Calculation:
- Profit/loss for each trade is calculated based on price difference and lot size.
4. Performance Reporting:
- Provides trade-by-trade summaries and a final balance.
Step 11: Evaluating and Fine-Tuning the Strategy
With the backtesting results, analyze the strategy’s performance metrics and adjust parameters for optimization.
Key Evaluation Metrics:
- Win Rate: Percentage of profitable trades.
- Drawdown: Largest decline in account balance from peak to trough.
- Sharpe Ratio: Risk-adjusted return over the test period.
- Risk-Reward Ratio: Comparison of average win size to average loss size.
Recommendations for Fine-Tuning:
- Adjusting RSI and Proximity Levels:
Experiment with different RSI periods and proximity thresholds to identify optimal levels. - Improving Filters:
Integrate additional indicators, like moving averages or Bollinger Bands, to minimize false signals. - Testing Across Markets:
Backtest the strategy on diverse assets and timeframes to gauge robustness.
Conclusion: Achieving Systematic Trading Mastery
This tutorial laid the foundation for a systematic trading framework combining RSI, support & resistance, and Python’s power. Here’s what you achieved:
- Implemented a Signal-Generating Algorithm:
Accurate detection of buy/sell opportunities using key indicators. - Developed Robust Visualizations:
Mapped signals and levels directly onto price charts for actionable insights. - Performed Comprehensive Backtesting:
Assessed historical performance and identified areas for improvement.
Now equipped with these tools, you can refine and adapt your strategy to evolving markets. Dive deeper into this framework, expand it with machine learning models, or use alternative indicators to unlock even greater potential.
Explore our recommended learning materials to keep advancing:
Happy trading! 🚀
Let me know if you’d like to add additional fine-tuning examples or expand any section further.
You can save up to 100% on a Tradingview subscription with my refer-a-friend link. When you get there, click on the Tradingview icon on the top-left of the page to get to the free plan if that’s what you want.
➡️Subscribe Me here ➡️ https://medium.com/@ayrat_murt/subscribe
I’ve got a lot to share in my upcoming blogs.
Optimizing Python Trading: Leveraging RSI with Support & Resistance for High-Accuracy Signals was originally published in The Capital on Medium, where people are continuing the conversation by highlighting and responding to this story.