Discover How a Simple Candle Pattern Strategy Achieved a 65% Win Rate in Backtests | by Aydar Murt | The Capital | Jan, 2025


Before implementing any trading strategy, preparing clean, structured data is crucial. Below is the Python script for managing this vital step:

import pandas as pd
import pandas_ta as ta
from tqdm import tqdm
import os
import numpy as np
import plotly.graph_objects as go

tqdm.pandas()
def read_csv_to_dataframe(file_path):
df = pd.read_csv(file_path)
df["Gmt time"] = df["Gmt time"].str.replace(".000", "")
df['Gmt time'] = pd.to_datetime(df['Gmt time'], format='%d.%m.%Y %H:%M:%S')
df = df[df.High != df.Low] # Remove invalid rows
df.set_index("Gmt time", inplace=True)
return df
def read_data_folder(folder_path="./data"):
dataframes = []
file_names = []
for file_name in tqdm(os.listdir(folder_path)):
if file_name.endswith('.csv'):
file_path = os.path.join(folder_path, file_name)
df = read_csv_to_dataframe(file_path)
dataframes.append(df)
file_names.append(file_name)
return dataframes, file_names

Step Breakdown:

  • Library Importation: Libraries such as pandas streamline data manipulation, pandas_ta calculates technical indicators, tqdm monitors progress, and plotly is used for visualization.
  • File Preprocessing: Files are read individually, and irrelevant rows are filtered out (e.g., rows where High equals Low are considered anomalies).
  • Timestamp Conversion: The datetime format is standardized to ensure consistent time-series indexing.
  • Efficiency for Bulk Data: The read_data_folder function enables processing multiple datasets, accommodating scenarios like multi-asset analysis.

The strategy relies on specific conditions met by sequential candlestick formations. Here is the function that evaluates the pattern:

def total_signal(df, current_candle):
current_pos = df.index.get_loc(current_candle)
c1 = df['High'].iloc[current_pos] > df['Close'].iloc[current_pos]
c2 = df['Close'].iloc[current_pos] > df['High'].iloc[current_pos-2]
c3 = df['High'].iloc[current_pos-2] > df['High'].iloc[current_pos-1]
c4 = df['High'].iloc[current_pos-1] > df['Low'].iloc[current_pos]
c5 = df['Low'].iloc[current_pos] > df['Low'].iloc[current_pos-2]
c6 = df['Low'].iloc[current_pos-2] > df['Low'].iloc[current_pos-1]
if c1 and c2 and c3 and c4 and c5 and c6:
return 2 # Signal to buy (long)
# Symmetrical conditions for short signals
c1 = df['Low'].iloc[current_pos] < df['Open'].iloc[current_pos]
c2 = df['Open'].iloc[current_pos] < df['Low'].iloc[current_pos-2]
c3 = df['Low'].iloc[current_pos-2] < df['Low'].iloc[current_pos-1]
c4 = df['Low'].iloc[current_pos-1] < df['High'].iloc[current_pos]
c5 = df['High'].iloc[current_pos] < df['High'].iloc[current_pos-2]
c6 = df['High'].iloc[current_pos-2] < df['High'].iloc[current_pos-1]
if c1 and c2 and c3 and c4 and c5 and c6:
return 1 # Signal to sell (short)
return 0

Step Breakdown:

  • Logic Explanation: Six conditions ensure precise detection of a specific candlestick sequence, defining entry points for both buy and sell signals.
  • Signal Output: The function returns a numerical signal (2 for long, 1 for short, 0 for no signal), which will be later interpreted during backtesting.
  • Error Minimization: By using strict logical conditions, false signals are minimized, ensuring the strategy operates on high-probability setups.

Visualizing entry and exit points is essential for verifying a strategy. Below are the functions for marking patterns and plotting them on candlestick charts:

def add_total_signal(df):
df['TotalSignal'] = df.progress_apply(lambda row: total_signal(df, row.name), axis=1)
return df

def add_pointpos_column(df, signal_column):
def pointpos(row):
if row[signal_column] == 2:
return row['Low'] - 1e-4
elif row[signal_column] == 1:
return row['High'] + 1e-4
return np.nan
df['pointpos'] = df.apply(lambda row: pointpos(row), axis=1)
return df

def plot_candlestick_with_signals(df, start_index, num_rows):
df_subset = df[start_index:start_index + num_rows]
fig = make_subplots(rows=1, cols=1)
fig.add_trace(go.Candlestick(
x=df_subset.index,
open=df_subset['Open'],
high=df_subset['High'],
low=df_subset['Low'],
close=df_subset['Close'],
name='Candlesticks'), row=1, col=1)
fig.add_trace(go.Scatter(
x=df_subset.index, y=df_subset['pointpos'], mode="markers",
marker=dict(size=10, color="MediumPurple", symbol='circle'),
name="Entry Points"), row=1, col=1)
fig.show()

Step Breakdown:

  • Integration with Signals: The add_total_signal function augments the dataset with generated trade signals.
  • Chart Enhancements: Entry points are marked as purple circles below or above candles, aligning visual feedback with strategy logic.
  • Customizability: The script accommodates customizations such as time ranges, making it adaptable for diverse datasets.

With the logic in place, it’s time to backtest. Here’s how to evaluate performance across various assets:

from backtesting import Strategy, Backtest

def SIGNAL():
return df.TotalSignal

class MyStrat(Strategy):
mysize = 0.1 # Trade size
slperc = 0.04 # Stop loss percentage
tpperc = 0.02 # Take profit percentage

def init(self):
self.signal1 = self.I(SIGNAL)

def next(self):
if self.signal1 == 2 and not self.position:
self.buy(size=self.mysize, sl=self.data.Close[-1] * (1 - self.slperc), tp=self.data.Close[-1] * (1 + self.tpperc))
elif self.signal1 == 1 and not self.position:
self.sell(size=self.mysize, sl=self.data.Close[-1] * (1 + self.slperc), tp=self.data.Close[-1] * (1 - self.tpperc))

Step Breakdown:

  • Stop Loss & Take Profit Levels: Adjustable percentages ensure robust risk management.
  • Signal Integration: The backtest dynamically interprets generated signals and executes trades accordingly.
  • Flexibility for Optimization: Parameters like mysize, slperc, and tpperc can be fine-tuned to maximize profitability.

Backtesting results on the S&P 500 index offered the following takeaways:

  • 65% Win Rate: Demonstrates consistent success under various market conditions.
  • Aggregated Return: Yielded a notable 71% return across the testing period.
  • Trade-Specific Insights: The best trade delivered a profit of 10.3%, while the worst drawdown was limited to 4.8%.

While this strategy excelled with equities, its performance on forex was less consistent, indicating potential for asset-specific refinement.

This candlestick pattern strategy demonstrates the power of blending traditional trading wisdom with cutting-edge automation. While its simplicity is an advantage, combining it with additional patterns or refining it for specific asset classes may unlock greater potential.

For step-by-step tutorials and Python code, visit our newsletter, where we dive even deeper into algorithmic strategies and trading technologies. Subscribe to stay updated with the latest advancements in automated trading!



Source link

Schreibe einen Kommentar

Deine E-Mail-Adresse wird nicht veröffentlicht. Erforderliche Felder sind mit * markiert