This article presents a dynamic trading strategy,
PivotPointStrategy, built upon the principles of classical
pivot points, enhanced with volume and momentum confirmations. The
strategy intelligently adapts to different timeframes by calculating
daily, weekly, and optionally monthly pivot levels, and trades based on
price interaction with these crucial support and resistance zones. A
fixed stop-loss mechanism is also integrated for risk management.
The PivotPointStrategy leverages the predictive power of
pivot points, which are widely used by traders to identify potential
turning points and key price levels. The strategy distinguishes itself
by:
touch or near
interactions with pivot levels, indicating potential bounces, and
breakout interactions, signaling continuation.Core Trading Logic:
The strategy aims for two primary types of trades around pivot levels:
All entry and exit signals are subject to the volume and momentum confirmation filters.
The strategy relies on dynamically calculating pivot points (PP) and their associated support (S1, S2, S3) and resistance (R1, R2, R3) levels based on the previous period’s High, Low, and Close prices.
The classical formulas for pivot points are:
Where \(H\), \(L\), and \(C\) are the High, Low, and Close prices of the previous period, respectively.
PivotPointStrategy ImplementationHere are the key components of the strategy:
import backtrader as bt
import yfinance as yf
import pandas as pd
import numpy as np
from datetime import datetime, timedelta
class PivotPointStrategy(bt.Strategy):
params = (
('use_daily', True), # Use daily pivot points
('use_weekly', True), # Use weekly pivot points
('use_monthly', False), # Use monthly pivot points
('bounce_threshold', 0.01), # 1% threshold for level interaction (near/touch)
('breakout_threshold', 0.03), # 3% threshold for breakouts
('volume_multiplier', 1.2), # Volume confirmation multiplier (1.2x average)
('volume_period', 30), # Volume average period
('rsi_period', 14), # RSI for momentum confirmation
('stop_loss_pct', 0.05), # 5% stop loss
)
def __init__(self):
# Data feeds
self.high = self.data.high
self.low = self.data.low
self.close = self.data.close
self.open = self.data.open
self.volume = self.data.volume
# Technical indicators
self.volume_sma = bt.indicators.SMA(self.volume, period=self.params.volume_period)
self.rsi = bt.indicators.RSI(period=self.params.rsi_period)
# Storage for calculated pivot levels (keyed by date/week/month start)
self.daily_pivots = {}
self.weekly_pivots = {}
self.monthly_pivots = {}
# OHLC data accumulators for multi-period pivot calculations
self.daily_ohlc = {'high': 0, 'low': 0, 'close': 0}
self.weekly_ohlc = {'high': 0, 'low': 0, 'close': 0}
self.monthly_ohlc = {'high': 0, 'low': 0, 'close': 0}
# Track last calculation dates to determine when to reset OHLC for new periods
self.last_daily_calc = None
self.last_weekly_calc = None
self.last_monthly_calc = None
# Order tracking
self.order = None # To prevent multiple orders
self.stop_order = None # To manage the stop-loss order
def calculate_pivot_levels(self, high, low, close):
"""Calculates classic pivot point levels."""
pp = (high + low + close) / 3
s1 = (2 * pp) - high
s2 = pp - (high - low)
s3 = low - 2 * (high - pp)
r1 = (2 * pp) - low
r2 = pp + (high - low)
r3 = high + 2 * (pp - low)
return {'PP': pp, 'S1': s1, 'S2': s2, 'S3': s3,
'R1': r1, 'R2': r2, 'R3': r3}
def update_ohlc_data(self):
"""
Aggregates OHLC data for daily, weekly, and monthly pivot calculations.
Calculates new pivots when a period rolls over.
"""
current_date = self.data.datetime.date(0)
current_high = self.high[0]
current_low = self.low[0]
current_close = self.close[0]
# Daily OHLC and Pivot Calculation
if self.last_daily_calc != current_date:
if self.last_daily_calc is not None:
# If it's a new day, calculate pivots from the *previous* day's aggregated OHLC
self.daily_pivots[current_date] = self.calculate_pivot_levels(
self.daily_ohlc['high'], self.daily_ohlc['low'], self.daily_ohlc['close']
)
# Reset OHLC for the new day
self.daily_ohlc = {'high': current_high, 'low': current_low, 'close': current_close}
self.last_daily_calc = current_date
else:
# Continue accumulating OHLC for the current day
self.daily_ohlc['high'] = max(self.daily_ohlc['high'], current_high)
self.daily_ohlc['low'] = min(self.daily_ohlc['low'], current_low)
self.daily_ohlc['close'] = current_close # Always use current close for daily pivot calc
# Weekly OHLC and Pivot Calculation (Week starts on Monday, weekday() returns 0 for Monday)
week_start = current_date - timedelta(days=current_date.weekday())
if self.last_weekly_calc != week_start:
if self.last_weekly_calc is not None:
self.weekly_pivots[week_start] = self.calculate_pivot_levels(
self.weekly_ohlc['high'], self.weekly_ohlc['low'], self.weekly_ohlc['close']
)
self.weekly_ohlc = {'high': current_high, 'low': current_low, 'close': current_close}
self.last_weekly_calc = week_start
else:
self.weekly_ohlc['high'] = max(self.weekly_ohlc['high'], current_high)
self.weekly_ohlc['low'] = min(self.weekly_ohlc['low'], current_low)
self.weekly_ohlc['close'] = current_close
# Monthly OHLC and Pivot Calculation
month_start = current_date.replace(day=1)
if self.last_monthly_calc != month_start:
if self.last_monthly_calc is not None:
self.monthly_pivots[month_start] = self.calculate_pivot_levels(
self.monthly_ohlc['high'], self.monthly_ohlc['low'], self.monthly_ohlc['close']
)
self.monthly_ohlc = {'high': current_high, 'low': current_low, 'close': current_close}
self.last_monthly_calc = month_start
else:
self.monthly_ohlc['high'] = max(self.monthly_ohlc['high'], current_high)
self.monthly_ohlc['low'] = min(self.monthly_ohlc['low'], current_low)
self.monthly_ohlc['close'] = current_close
def get_current_pivot_levels(self):
"""Retrieves and consolidates all active pivot levels for the current bar."""
current_date = self.data.datetime.date(0)
levels = []
# Add daily pivots if enabled and available for the current day (yesterday's calculation)
if self.params.use_daily and (current_date - timedelta(days=1)) in self.daily_pivots:
daily_levels_date = current_date - timedelta(days=1) # Pivots are for today, based on yesterday's data
if daily_levels_date in self.daily_pivots:
daily = self.daily_pivots[daily_levels_date]
for level_name, level_value in daily.items():
levels.append((level_value, 'daily', level_name))
# Add weekly pivots if enabled and available
if self.params.use_weekly:
prev_week_start = current_date - timedelta(days=current_date.weekday() + 7) # Start of previous week
if prev_week_start in self.weekly_pivots:
weekly = self.weekly_pivots[prev_week_start]
for level_name, level_value in weekly.items():
levels.append((level_value, 'weekly', level_name))
# Add monthly pivots if enabled and available
if self.params.use_monthly:
prev_month_start = (current_date.replace(day=1) - timedelta(days=1)).replace(day=1) # Start of previous month
if prev_month_start in self.monthly_pivots:
monthly = self.monthly_pivots[prev_month_start]
for level_name, level_value in monthly.items():
levels.append((level_value, 'monthly', level_name))
# Sort levels for easier searching (e.g., finding nearest)
return sorted(levels, key=lambda x: x[0])
def check_level_interaction(self, price, high, low):
"""
Determines if the current bar's price action interacts with any pivot levels
(bounce or breakout).
"""
levels = self.get_current_pivot_levels()
for level_price, timeframe, level_name in levels:
# Check for 'touch' or 'near' interaction (potential bounce)
distance_pct = abs(price - level_price) / level_price
if distance_pct <= self.params.bounce_threshold:
# If current bar touches or crosses the level
if (low <= level_price <= high):
return 'touch', level_price, timeframe, level_name
# Or if very close
elif distance_pct <= self.params.bounce_threshold / 2:
return 'near', level_price, timeframe, level_name
# Check for breakouts
elif distance_pct <= self.params.breakout_threshold:
# Support breakout: Close below level, and level was within bar range
if level_name.startswith('S') and price < level_price and low <= level_price <= high:
return 'support_break', level_price, timeframe, level_name
# Resistance breakout: Close above level, and level was within bar range
elif level_name.startswith('R') and price > level_price and low <= level_price <= high:
return 'resistance_break', level_price, timeframe, level_name
return None, None, None, None # No significant interaction
def volume_confirmation(self):
"""Checks if current volume is above the average volume by a multiplier."""
# Ensure enough data for volume SMA and handle division by zero
if np.isnan(self.volume_sma[0]) or self.volume_sma[0] == 0:
return True # No average, assume confirmation (or add a warning)
return self.volume[0] > self.volume_sma[0] * self.params.volume_multiplier
def momentum_confirmation(self, trade_direction):
"""Checks RSI for momentum confirmation."""
# Ensure RSI has enough data
if np.isnan(self.rsi[0]):
return True # No RSI, assume confirmation (or add a warning)
if trade_direction == 'long':
# For long, RSI should not be overbought (above 70) and not extremely oversold (below 30)
return self.rsi[0] > 30 and self.rsi[0] < 70
elif trade_direction == 'short':
# For short, RSI should not be oversold (below 30) and not extremely overbought (above 70)
return self.rsi[0] > 30 and self.rsi[0] < 70
return True # Default if direction is not specified
def notify_order(self, order):
# Handles order completion (execution) or failure (canceled/rejected)
if order.status in [order.Completed]:
if order.isbuy() and self.position.size > 0: # Check if it's an entry buy
stop_price = order.executed.price * (1 - self.params.stop_loss_pct)
self.stop_order = self.sell(exectype=bt.Order.Stop, price=stop_price, size=self.position.size)
self.log(f'BUY EXECUTED, Price: {order.executed.price:.2f}, Setting Stop Loss at {stop_price:.2f}')
elif order.issell() and self.position.size < 0: # Check if it's an entry sell (short)
stop_price = order.executed.price * (1 + self.params.stop_loss_pct)
self.stop_order = self.buy(exectype=bt.Order.Stop, price=stop_price, size=abs(self.position.size))
self.log(f'SELL EXECUTED, Price: {order.executed.price:.2f}, Setting Stop Loss at {stop_price:.2f}')
elif order.isbuy() and self.position.size == 0 and self.stop_order and order.ref == self.stop_order.ref: # Stop-loss buy (cover short)
self.log(f'STOP LOSS (COVER SHORT) EXECUTED, Price: {order.executed.price:.2f}')
elif order.issell() and self.position.size == 0 and self.stop_order and order.ref == self.stop_order.ref: # Stop-loss sell (exit long)
self.log(f'STOP LOSS (EXIT LONG) EXECUTED, Price: {order.executed.price:.2f}')
self.order = None # Clear general order reference
if self.stop_order and order.ref == self.stop_order.ref: # If the completed order was the stop order itself
self.stop_order = None # Clear stop order reference
elif order.status in [order.Canceled, order.Rejected, order.Margin]:
self.log(f'Order Canceled/Rejected/Margin: Status {order.getstatusname()}')
self.order = None # Clear entry order reference
if self.stop_order and order.ref == self.stop_order.ref:
self.log("WARNING: Stop-Loss Order failed!", doprint=True)
self.stop_order = None
def next(self):
# Ensure no pending orders and enough data for indicators
if self.order is not None:
return
# Minimum data needed for indicators to warm up
min_data_needed = max(self.params.volume_period, self.params.rsi_period)
if len(self.data) < min_data_needed + 1: # +1 for current bar data
return
# Update OHLC data and calculate pivots for the *next* day/week/month
# (These are pivot points for the *current* bar's date, based on previous period's OHLC)
self.update_ohlc_data()
current_price = self.close[0]
current_high = self.high[0]
current_low = self.low[0]
# Check interaction with pivot levels
interaction, level_price, timeframe, level_name = self.check_level_interaction(
current_price, current_high, current_low
)
if interaction is None: # No significant interaction with any level
return
# Trading logic based on pivot level interactions
# BOUNCE STRATEGY (Price touches/nears level and reverses)
if interaction == 'touch' or interaction == 'near':
if level_name.startswith('S'): # Support level - expect bounce UP (buy signal)
if self.momentum_confirmation('long') and self.volume_confirmation():
if self.position.size < 0: # Close existing short position
if self.stop_order is not None: self.cancel(self.stop_order)
self.order = self.close()
self.log(f'Close SHORT on {level_name} ({timeframe}) bounce signal at {current_price:.2f}')
elif not self.position: # Open new long position
self.order = self.buy()
self.log(f'BUY on {level_name} ({timeframe}) bounce signal at {current_price:.2f}')
elif level_name.startswith('R'): # Resistance level - expect bounce DOWN (sell/short signal)
if self.momentum_confirmation('short') and self.volume_confirmation():
if self.position.size > 0: # Close existing long position
if self.stop_order is not None: self.cancel(self.stop_order)
self.order = self.close()
self.log(f'Close LONG on {level_name} ({timeframe}) bounce signal at {current_price:.2f}')
elif not self.position: # Open new short position
self.order = self.sell()
self.log(f'SELL on {level_name} ({timeframe}) bounce signal at {current_price:.2f}')
# BREAKOUT STRATEGY (Price breaks through level with confirmation)
elif interaction == 'resistance_break': # Break above resistance - go long
if self.momentum_confirmation('long') and self.volume_confirmation():
if self.position.size < 0: # Close existing short position
if self.stop_order is not None: self.cancel(self.stop_order)
self.order = self.close()
self.log(f'Close SHORT on {level_name} ({timeframe}) breakout signal at {current_price:.2f}')
elif not self.position: # Open new long position
self.order = self.buy()
self.log(f'BUY on {level_name} ({timeframe}) breakout signal at {current_price:.2f}')
elif interaction == 'support_break': # Break below support - go short
if self.momentum_confirmation('short') and self.volume_confirmation():
if self.position.size > 0: # Close existing long position
if self.stop_order is not None: self.cancel(self.stop_order)
self.order = self.close()
self.log(f'Close LONG on {level_name} ({timeframe}) breakout signal at {current_price:.2f}')
elif not self.position: # Open new short position
self.order = self.sell()
self.log(f'SELL on {level_name} ({timeframe}) breakout signal at {current_price:.2f}')
def stop(self):
# Optional: Log final portfolio value at the end of the backtest
print(f'Final Portfolio Value: {self.broker.getvalue():,.2f}')Explanation of PivotPointStrategy:
params: Defines various configurable
parameters, including which pivot timeframes to use
(use_daily, use_weekly,
use_monthly), sensitivity thresholds for
bounce and breakout interactions,
volume_multiplier, rsi_period, and
stop_loss_pct.__init__(self):
high,
low, close, open,
volume).bt.indicators.SMA for volume and
bt.indicators.RSI for momentum.daily_pivots,
weekly_pivots, monthly_pivots) to store
calculated pivot levels._ohlc dictionaries to accumulate OHLC data
for the respective periods, and last_calc variables to
track when to reset these aggregations and calculate new pivot
levels.self.order and self.stop_order
for managing trade execution.calculate_pivot_levels(self, high, low, close):
A helper method that implements the standard pivot point formulas based
on the provided High, Low, and Close for a period.update_ohlc_data(self): This crucial
method runs on every bar. It intelligently tracks the high,
low, and close for the current daily,
weekly, and monthly periods. When a new day, week, or month begins, it
takes the final OHLC of the previous period to
calculate the pivot levels for the new period, storing them in
the respective dictionaries. This ensures pivots are based on completed
prior periods, as is standard practice.get_current_pivot_levels(self): This
method gathers all currently active pivot levels (daily, weekly,
monthly, if enabled) that were calculated from the prior
periods and returns them as a sorted list. This is key because pivot
levels for the current day/week/month are derived from the
previous day’s/week’s/month’s data.check_level_interaction(self, price, high, low):
Determines how the current bar’s price action (price,
high, low) is interacting with the known pivot
levels. It checks if the price is within a bounce_threshold
(potential reversal) or crosses significantly beyond a
breakout_threshold (potential continuation).volume_confirmation(self): Checks if
the current bar’s volume is sufficiently higher than its
volume_sma to confirm the strength of a price move.momentum_confirmation(self, trade_direction):
Uses the RSI to confirm momentum. For both long and short
trades, it ensures RSI is not in extreme overbought/oversold regions
(e.g., between 30 and 70), indicating sustained momentum rather than a
reversal from exhaustion.notify_order(self, order): Handles
order completion and failure. When an entry order is filled, it
immediately places a fixed percentage stop_loss_pct
bt.Order.Stop order to manage risk. It also logs execution
details.next(self): This is the core strategy
logic, executed on each new bar.
update_ohlc_data()
to prepare data for the next period’s pivot calculations.check_level_interaction() to identify how the current price
is interacting with pivot levels.touch or near interaction with a
Support level occurs, and volume/momentum are
confirmed, it closes any existing short position or opens a new long
position (bounce trade).touch or near interaction with a
Resistance level occurs, and volume/momentum are
confirmed, it closes any existing long position or opens a new short
position (bounce trade).The provided script includes two primary ways to test this strategy:
Single Backtest (for initial testing and plotting):
if __name__=='__main__':
# Download data and run backtest (Example for ETH-USD)
data = yf.download('ETH-USD', '2020-01-01', '2024-01-01', auto_adjust=False) # Use auto_adjust=False as per preference
data.columns = data.columns.droplevel(1) if isinstance(data.columns, pd.MultiIndex) else data.columns # Droplevel if MultiIndex
data_feed = bt.feeds.PandasData(dataname=data)
cerebro = bt.Cerebro()
cerebro.addstrategy(PivotPointStrategy)
cerebro.adddata(data_feed)
cerebro.addsizer(bt.sizers.PercentSizer, percents=95)
cerebro.broker.setcash(100000)
cerebro.broker.setcommission(commission=0.001)
print(f'Start: ${cerebro.broker.getvalue():,.2f}')
results = cerebro.run()
print(f'End: ${cerebro.broker.getvalue():,.2f}')
print(f'Return: {((cerebro.broker.getvalue() / 100000) - 1) * 100:.2f}%')
# Fix matplotlib plotting issues (already in your code)
plt.rcParams['figure.max_open_warning'] = 0
plt.rcParams['agg.path.chunksize'] = 10000
try:
cerebro.plot(iplot=False, style='candlestick', volume=True) # Added candlestick and volume
plt.show()
except Exception as e:
print(f"Plotting error: {e}")
print("Strategy completed successfully - plotting skipped")This block allows you to quickly run the strategy over a single
period and visualize its trades using
cerebro.plot().
Rolling Backtest (for robustness evaluation): This method is crucial for understanding a strategy’s consistency across various market conditions, preventing curve-fitting to a single historical period.
import dateutil.relativedelta as rd # Added import
import seaborn as sns # Added import
# Define the strategy for the rolling backtest
strategy = PivotPointStrategy
def run_rolling_backtest(
ticker="BTC-USD",
start="2018-01-01",
end="2025-06-21", # Updated end date to current date
window_months=3,
strategy_params=None
):
strategy_params = strategy_params or {}
all_results = []
start_dt = pd.to_datetime(start)
end_dt = pd.to_datetime(end)
current_start = start_dt
while True:
current_end = current_start + rd.relativedelta(months=window_months)
if current_end > end_dt:
current_end = end_dt
if current_start >= current_end:
break
print(f"\nROLLING BACKTEST: {current_start.date()} to {current_end.date()}")
# Data download using yfinance, respecting user's preference
# Using the saved preference: yfinance download with auto_adjust=False and droplevel(axis=1, level=1)
data = yf.download(ticker, start=current_start, end=current_end, auto_adjust=False, progress=False)
# Apply droplevel if data is a MultiIndex, as per user's preference
if isinstance(data.columns, pd.MultiIndex):
data = data.droplevel(1, axis=1)
# Check for sufficient data for strategy warm-up
# PivotPointStrategy needs data for volume_period and rsi_period.
# Also, pivot calculations need at least 1 day/week/month of prior data.
# So, min_bars_needed should consider the longest period for indicators + some buffer for pivot calc.
vol_period = strategy_params.get('volume_period', PivotPointStrategy.params.volume_period)
rsi_period = strategy_params.get('rsi_period', PivotPointStrategy.params.rsi_period)
min_bars_needed = max(vol_period, rsi_period) + 30 # Rough buffer for pivots and general stability
if data.empty or len(data) < min_bars_needed:
print(f"Not enough data for period {current_start.date()} to {current_end.date()} (requires at least {min_bars_needed} bars). Skipping.")
if current_end == end_dt:
break
current_start = current_end
continue
feed = bt.feeds.PandasData(dataname=data)
cerebro = bt.Cerebro()
cerebro.addstrategy(strategy, **strategy_params)
cerebro.adddata(feed)
cerebro.broker.setcash(100000)
cerebro.broker.setcommission(commission=0.001)
cerebro.addsizer(bt.sizers.PercentSizer, percents=95)
start_val = cerebro.broker.getvalue()
cerebro.run()
final_val = cerebro.broker.getvalue()
ret = (final_val - start_val) / start_val * 100
all_results.append({
'start': current_start.date(),
'end': current_end.date(),
'return_pct': ret,
'final_value': final_val,
})
print(f"Return: {ret:.2f}% | Final Value: {final_val:.2f}")
if current_end == end_dt:
break
current_start = current_end
return pd.DataFrame(all_results)
def report_stats(df):
returns = df['return_pct']
stats = {
'Mean Return %': np.mean(returns),
'Median Return %': np.median(returns),
'Std Dev %': np.std(returns),
'Min Return %': np.min(returns),
'Max Return %': np.max(returns),
'Sharpe Ratio': np.mean(returns) / np.std(returns) if np.std(returns) > 0 else np.nan
}
print("\n=== ROLLING BACKTEST STATISTICS ===")
for k, v in stats.items():
print(f"{k}: {v:.2f}")
return stats
def plot_four_charts(df, rolling_sharpe_window=4):
"""
Generates four analytical plots for rolling backtest results.
"""
fig, ((ax1, ax2), (ax3, ax4)) = plt.subplots(2, 2, figsize=(12, 8)) # Adjusted figsize for clarity
periods = list(range(len(df)))
returns = df['return_pct']
# 1. Period Returns (Top Left)
colors = ['green' if r >= 0 else 'red' for r in returns]
ax1.bar(periods, returns, color=colors, alpha=0.7)
ax1.set_title('Period Returns', fontsize=14, fontweight='bold')
ax1.set_xlabel('Period')
ax1.set_ylabel('Return %')
ax1.axhline(y=0, color='black', linestyle='-', alpha=0.3)
ax1.grid(True, alpha=0.3)
# 2. Cumulative Returns (Top Right)
cumulative_returns = (1 + returns / 100).cumprod() * 100 - 100
ax2.plot(periods, cumulative_returns, marker='o', linewidth=2, markersize=4, color='blue')
ax2.set_title('Cumulative Returns', fontsize=14, fontweight='bold')
ax2.set_xlabel('Period')
ax2.set_ylabel('Cumulative Return %')
ax2.grid(True, alpha=0.3)
# 3. Rolling Sharpe Ratio (Bottom Left)
rolling_sharpe = returns.rolling(window=rolling_sharpe_window).apply(
lambda x: x.mean() / x.std() if x.std() > 0 else np.nan, raw=False
)
valid_mask = ~rolling_sharpe.isna()
valid_periods = [i for i, valid in enumerate(valid_mask) if valid]
valid_sharpe = rolling_sharpe[valid_mask]
ax3.plot(valid_periods, valid_sharpe, marker='o', linewidth=2, markersize=4, color='orange')
ax3.axhline(y=0, color='red', linestyle='--', alpha=0.5)
ax3.set_title(f'Rolling Sharpe Ratio ({rolling_sharpe_window}-period)', fontsize=14, fontweight='bold')
ax3.set_xlabel('Period')
ax3.set_ylabel('Sharpe Ratio')
ax3.grid(True, alpha=0.3)
# 4. Return Distribution (Bottom Right)
bins = min(15, max(5, len(returns)//2))
ax4.hist(returns, bins=bins, alpha=0.7, color='steelblue', edgecolor='black')
mean_return = returns.mean()
ax4.axvline(mean_return, color='red', linestyle='--', linewidth=2,
label=f'Mean: {mean_return:.2f}%')
ax4.set_title('Return Distribution', fontsize=14, fontweight='bold')
ax4.set_xlabel('Return %')
ax4.set_ylabel('Frequency')
ax4.legend()
ax4.grid(True, alpha=0.3)
plt.tight_layout()
plt.show()
if __name__ == '__main__':
# The end date is set to the current date for a more up-to-date backtest.
current_date = datetime.now().date()
df = run_rolling_backtest(
ticker="BTC-USD",
start="2018-01-01",
end=current_date, # Use the current date
window_months=3,
)
print("\n=== ROLLING BACKTEST RESULTS ===")
print(df)
stats = report_stats(df)
plot_four_charts(df)The PivotPointStrategy offers a robust and adaptable
approach to trading by integrating multi-timeframe pivot point analysis
with essential volume and momentum confirmations. Its ability to
identify both bounce and breakout opportunities, combined with a
disciplined stop-loss mechanism, provides a comprehensive framework for
navigating various market conditions. The use of rolling backtesting is
crucial for validating the strategy’s consistency and effectiveness over
different periods, offering more reliable insights than a single, long
backtest. Further optimization of parameters (bounce/breakout
thresholds, volume/RSI periods, stop loss) for specific assets and
market regimes could further enhance its performance.