Pattern & Indicator Confluence
Build a confluence engine that fires only when chart patterns and technical indicators agree — reducing noise and improving precision.
Pattern + Indicator Confluence System
Core Logic
This section describes the fundamental principles behind the Pattern + Indicator Confluence trading system, outlining how candlestick patterns are combined with technical indicators for signal generation.
Pattern + Indicator Confluence combines candlestick pattern signals with technical indicator confirmation, requiring both to agree before emitting a trade signal.
Logic:
- Detect hammer (bullish) and shooting star (bearish) single-candle patterns.
- Confirm with RSI: RSI < 40 confirms bullish patterns; RSI > 60 confirms bearish.
- Signal is only emitted when pattern AND indicator agree.
Limitation: This dual-confirmation approach reduces signal frequency significantly; a minimum dataset of 1000+ bars is recommended for statistical validity.
import pandas as pd
import numpy as np
import plotly.graph_objects as go
from plotly.subplots import make_subplotsData Generation and Application
This section covers the generation of synthetic OHLCV data, which is then fed into the pattern_indicator_confluence_system function to produce trading signals. The output shows the counts of different patterns and signals detected.
def generate_data(periods: int) -> pd.DataFrame:
"""
Generate synthetic OHLCV price data using a geometric random walk.
Parameters
----------
periods : int
Number of 1-minute bars to generate.
Returns
-------
pd.DataFrame
DataFrame with columns: open, high, low, close, volume, datetime.
"""
start_date = pd.to_datetime("2024-01-01 00:00:00+00:00")
datetime_index = pd.date_range(start_date, periods=periods, freq="1min", tz="UTC")
price_data = []
last_close = 42000
for i in range(periods):
open_price = last_close + np.random.normal(0, last_close * 0.0005)
close_price = open_price + np.random.normal(0, last_close * 0.005)
body_high = max(open_price, close_price)
body_low = min(open_price, close_price)
high_price = max(body_high + abs(np.random.normal(0, last_close * 0.002)), open_price, close_price)
low_price = min(body_low - abs(np.random.normal(0, last_close * 0.002)), open_price, close_price)
if high_price < low_price:
high_price, low_price = low_price, high_price
price_data.append({
"open": max(1, int(open_price)),
"high": max(1, int(high_price)),
"low": max(1, int(low_price)),
"close": max(1, int(close_price)),
})
last_close = close_price
df = pd.DataFrame(price_data, index=datetime_index)
df.index.name = "datetime"
df["volume"] = np.random.uniform(100.0, 500.0, periods)
df["datetime"] = df.index.to_series()
return df.reset_index(drop=True)
df = generate_data(500)
display(df.head())| open | high | low | close | volume | datetime | |
|---|---|---|---|---|---|---|
| 0 | 42030 | 42101 | 41918 | 41934 | 286.774763 | 2024-01-01 00:00:00+00:00 |
| 1 | 41923 | 42234 | 41788 | 42229 | 328.672052 | 2024-01-01 00:01:00+00:00 |
| 2 | 42212 | 42278 | 41959 | 42036 | 112.342922 | 2024-01-01 00:02:00+00:00 |
| 3 | 42047 | 42150 | 41965 | 42090 | 220.524604 | 2024-01-01 00:03:00+00:00 |
| 4 | 42136 | 42142 | 41969 | 41994 | 406.657245 | 2024-01-01 00:04:00+00:00 |
Function
This section defines the pattern_indicator_confluence_system function, which implements the core logic described above. It takes OHLCV data and RSI parameters as input and returns a DataFrame with detected patterns, RSI values, and final trading signals.
def pattern_indicator_confluence_system(
df: pd.DataFrame,
rsi_period: int = 14,
rsi_bull_level: float = 40.0,
rsi_bear_level: float = 60.0,
) -> pd.DataFrame:
"""
Combine candlestick pattern detection with RSI confirmation.
Core logic
----------
1. Detect Hammer (bullish) and Shooting Star (bearish) candle patterns
using body/range and wick/body ratios.
2. Compute RSI for momentum confirmation.
3. Emit Buy (+1) only when a Hammer AND RSI < rsi_bull_level.
4. Emit Sell (-1) only when a Shooting Star AND RSI > rsi_bear_level.
Parameters
----------
df : pd.DataFrame OHLCV DataFrame.
rsi_period : int RSI calculation period.
rsi_bull_level : float RSI threshold for bullish confirmation.
rsi_bear_level : float RSI threshold for bearish confirmation.
Returns
-------
pd.DataFrame with: pattern, rsi, pattern_signal, rsi_signal, signal.
"""
df = df.copy().sort_values("datetime", ignore_index=True)
# ── Candle geometry ───────────────────────────────────────────────────────
df["body"] = (df["close"] - df["open"]).abs()
df["full_range"] = df["high"] - df["low"]
df["upper_wick"] = df["high"] - df[["open","close"]].max(axis=1)
df["lower_wick"] = df[["open","close"]].min(axis=1) - df["low"]
range_safe = df["full_range"].replace(0, np.nan)
body_safe = df["body"].replace(0, np.nan)
# ── Pattern detection ─────────────────────────────────────────────────────
hammer = (
(df["lower_wick"] >= 2 * body_safe) &
(df[["open","close"]].min(axis=1) > df["low"] + 0.7 * df["full_range"])
)
shooting_star = (
(df["upper_wick"] >= 2 * body_safe) &
(df[["open","close"]].max(axis=1) < df["low"] + 0.3 * df["full_range"])
)
df["pattern"] = "none"
df["pattern_signal"] = 0
df.loc[hammer, "pattern"] = "hammer"
df.loc[shooting_star, "pattern"] = "shooting_star"
df.loc[hammer, "pattern_signal"] = 1
df.loc[shooting_star, "pattern_signal"] = -1
# ── RSI ───────────────────────────────────────────────────────────────────
delta = df["close"].diff()
gain = delta.clip(lower=0).rolling(rsi_period).mean()
loss = (-delta.clip(upper=0)).rolling(rsi_period).mean()
df["rsi"] = 100 - 100 / (1 + gain / loss.replace(0, np.nan))
df["rsi_signal"] = np.where(df["rsi"] < rsi_bull_level, 1,
np.where(df["rsi"] > rsi_bear_level, -1, 0))
# ── Confluence signal ─────────────────────────────────────────────────────
df["signal"] = 0
df.loc[(df["pattern_signal"] == 1) & (df["rsi_signal"] == 1), "signal"] = 1
df.loc[(df["pattern_signal"] == -1) & (df["rsi_signal"] == -1), "signal"] = -1
return dfdf_signals = pattern_indicator_confluence_system(df)
print(df_signals["pattern"].value_counts())
print(df_signals["signal"].value_counts())pattern none 477 hammer 15 shooting_star 8 Name: count, dtype: int64 signal 0 493 -1 5 1 2 Name: count, dtype: int64
Explanation of Parameters
This section clarifies the significance of the parameters used in the pattern_indicator_confluence_system, particularly how the dual-condition gate and adjusted RSI thresholds influence signal generation.
- Dual-condition gate: Both
pattern_signal == Xandrsi_signal == Xmust be true simultaneously; a single condition is insufficient to trigger the final signal. - RSI thresholds of 40/60 (rather than the classic 30/70) are intentionally looser, increasing the number of qualifying RSI conditions to compensate for the rarity of confirmed candle patterns.
Visualization
This section provides the code to visualize the generated OHLCV data, overlaid with the detected buy and sell signals, and the corresponding RSI values. This plot helps in understanding the system's output visually.
buy_signals = df_signals[df_signals["signal"] == 1]
sell_signals = df_signals[df_signals["signal"] == -1]
fig = make_subplots(rows=2, cols=1, shared_xaxes=True,
subplot_titles=["Price + Confluence Signals", "RSI"],
row_heights=[0.65, 0.35])
fig.add_trace(go.Candlestick(x=df_signals["datetime"],
open=df_signals["open"], high=df_signals["high"],
low=df_signals["low"], close=df_signals["close"], name="Price"), row=1, col=1)
fig.add_trace(go.Scatter(x=buy_signals["datetime"], y=buy_signals["low"] * 0.999,
mode="markers", marker=dict(symbol="triangle-up", size=10, color="green"), name="Buy"), row=1, col=1)
fig.add_trace(go.Scatter(x=sell_signals["datetime"], y=sell_signals["high"] * 1.001,
mode="markers", marker=dict(symbol="triangle-down", size=10, color="red"), name="Sell"), row=1, col=1)
fig.add_trace(go.Scatter(x=df_signals["datetime"], y=df_signals["rsi"],
mode="lines", name="RSI", line=dict(color="purple")), row=2, col=1)
for lvl, clr in [(40, "green"), (60, "red")]:
fig.add_hline(y=lvl, line_dash="dot", line_color=clr, row=2, col=1)
fig.update_layout(title_text="Pattern + Indicator Confluence",
xaxis_rangeslider_visible=False, height=700, xaxis2_title="Datetime")
fig.show()