Title: Trading model for breakout strategy
Tags: python,optimization,trading

Question: I'm trying to build a model using historical price and volume data.
Decision Variables
The model should be able to decide:

When to enter a trade
What type of trade to enter (Long/Short)
How much SL and TP to keep for a trade
When to exit a trade

Things to keep in mind

The model should at no point take into consideration any future price or volume data when making a trading decision. E.g., a decision to enter trade should be purely made on the price or volume data from the past, it should have no influence of future. This is to simulate the real-world where future price or volume movements are not available anyway.
The model can use multiple validations to achieve the goals
Multiple time horizons can be looked at (e.g., 1-min candles, 2-min candles, 5-mins, etc.).

Goals
The overall targets to achieve through the model are:
Daily cumulative trading returns: >= 10% (cumulative = sum of all trades)

Sharpe Ratio: >= 3.0
Sortino Ratio: >= 5.0
Profit Factor: >= 2.0
Win Rate: >=75% (no. of positive trades / total trades)

Approach 1 (using TA-Lib):
import talib as ta
import math
import matplotlib.pyplot as plt

# Parameters
length = 14
k = 1.0
method = 'Atr'
show = False

# Data
# Replace [...] with your price and volume data
close = [...]
high = [...]
low = [...]
volume = [...]

# Calculate pivot highs and lows
ph = find_pivot_highs(high, length)
pl = find_pivot_lows(low, length)


def find_pivot_highs(data, length):
    pivot_highs = []
    for i in range(length, len(data) - length):
        if (
            data[i] > max(data[i - length : i])
            and data[i] > max(data[i + 1 : i + length + 1])
        ):
            pivot_highs.append(i)
    return pivot_highs


def find_pivot_lows(data, length):
    pivot_lows = []
    for i in range(length, len(data) - length):
        if (
            data[i] < min(data[i - length : i])
            and data[i] < min(data[i + 1 : i + length + 1])
        ):
            pivot_lows.append(i)
    return pivot_lows


# Calculate slope
def calculate_slope():
    if method == 'Atr':
        return ta.ATR(high, low, close, length) / length * k
    elif method == 'Stdev':
        return ta.STDDEV(src, length) / length * k
    elif method == 'Linreg':
        sma1 = ta.SMA(src * n, length)
        sma2 = ta.SMA(src, length)
        return math.fabs(sma1 - sma2 * ta.SMA(n, length)) / ta.VAR(n, length) / 2 * k


slope = calculate_slope()

slope_ph = [slope[i] if i in ph else slope_ph[i-1] for i in range(len(close))]
slope_pl = [slope[i] if i in pl else slope_pl[i-1] for i in range(len(close))]

upper = [ph[i] if i in ph else upper[i-1] - slope_ph[i] for i in range(len(close))]
lower = [pl[i] if i in pl else lower[i-1] + slope_pl[i] for i in range(len(close))]

# ----
single_upper = [0] * len(close)
single_lower = [0] * len(close)

for i in range(length, len(close)):
    if close[i] > upper[i-length]:
        single_upper[i] = 1
    elif i in ph:
        single_upper[i] = single_upper[i-1]

    if close[i] < lower[i-length]:
        single_lower[i] = 1
    elif i in pl:
        single_lower[i] = single_lower[i-1]

upper_breakout = [
    single_upper[i-1] and close[i] > upper[i] and (show or close[i] > close[i-length])
    for i in range(len(close))
]

lower_breakout = [
    single_lower[i-1] and close[i] < lower[i] and (show or close[i] < close[i-length])
    for i in range(len(close))
]

# Trading strategy
trades = []
trade_type = None
entry_price = None
stop_loss = None
take_profit = None

for i in range(len(close)):
    if trade_type is None:
        if upper_breakout[i]:
            trade_type = 'Long'
            entry_price = close[i]
            stop_loss = entry_price - 0.02 * entry_price  # Example stop loss calculation
            take_profit = entry_price + 0.03 * entry_price  # Example take profit calculation
        elif lower_breakout[i]:
            trade_type = 'Short'
            entry_price = close[i]
            stop_loss = entry_price + 0.02 * entry_price  # Example stop loss calculation
            take_profit = entry_price - 0.03 * entry_price  # Example take profit calculation
    else:
        if trade_type == 'Long':
            if close[i] <= stop_loss or close[i] >= take_profit:
                trades.append((entry_price, stop_loss, take_profit))
                trade_type = None
                entry_price = None
                stop_loss = None
                take_profit = None
        elif trade_type == 'Short':
            if close[i] >= stop_loss or close[i] <= take_profit:
                trades.append((entry_price, stop_loss, take_profit))
                trade_type = None
                entry_price = None
                stop_loss = None
                take_profit = None

# Calculate metrics
total_trades = len(trades)
positive_trades = sum(1 for t in trades if t[2] > t[0])
win_rate = positive_trades / total_trades if total_trades > 0 else 0

cumulative_returns = sum((t[2] - t[0]) / t[0] for t in trades)
sharpe_ratio = (cumulative_returns - 0.01) / (cumulative_returns.std() + 1e-9)
sortino_ratio = (cumulative_returns - 0.01) / (cumulative_returns[cumulative_returns < 0].std() + 1e-9)
profit_factor = sum(t[2] - t[0] for t in trades if t[2] > t[0]) / abs(sum(t[2] - t[0] for t in trades if t[2] < t[0]))

# Print metrics
print(f"Total Trades: {total_trades}")
print(f"Positive Trades: {positive_trades}")
print(f"Win Rate: {win_rate * 100}%")
print(f"Cumulative Returns: {cumulative_returns * 100}%")
print(f"Sharpe Ratio: {sharpe_ratio}")
print(f"Sortino Ratio: {sortino_ratio}")
print(f"Profit Factor: {profit_factor}")

# Plotting
plt.plot(close[length:], label='Closing Price')
plt.plot(upper[length:], label='Upper Trendline', color='#26a69a')
plt.plot(lower[length:], label='Lower Trendline', color='#ef5350')

for i in range(len(close)):
    if upper_breakout[i]:
        plt.scatter(i, close[i-length], marker='^', color='r', label='Upper Break')
    if lower_breakout[i]:
        plt.scatter(i, close[i-length], marker='v', color='g', label='Lower Break')

plt.legend()
plt.show()


Approach 2 (using pandas_ta):
import pandas as pd
import pandas_ta as ta
import matplotlib.pyplot as plt
import numpy as np

# Load your price data into a pandas DataFrame
data = pd.read_csv('your_data.csv')
close = data['close']
high = data['high']
low = data['low']

length = 14
k = 1.0
method = 'Atr'
show = False

# Calculate the indicators using pandas-ta
data['atr'] = ta.atr(high, low, close, length)
data['stddev'] = ta.stddev(close, length)
data['sma1'] = ta.sma(close, length)
data['sma2'] = ta.sma(close, length)
data['var'] = ta.var(close, length)
data['n'] = data.index

data['slope'] = 0.0
data['slope'] = np.where(method == 'Atr', data['atr'] / length * k,
                         np.where(method == 'Stdev', data['stddev'] / length * k,
                                  np.where(method == 'Linreg', data['sma1'] - data['sma2'] * data['n'] / data['var'] / 2 * k)))
#data.loc[method == 'Atr', 'slope'] = data['atr'] / length * k
#data.loc[method == 'Stdev', 'slope'] = data['stddev'] / length * k
#data.loc[method == 'Linreg', 'slope'] = (data['sma1'] - data['sma2'] * data['n']) / data['var'] / 2 * k

# Find pivot highs and lows
data['ph'] = ta.pivot_high(high, length)
data['pl'] = ta.pivot_low(low, length)

data['slope_ph'] = data['slope'].where(data.index.isin(data['ph'])).ffill()
data['slope_pl'] = data['slope'].where(data.index.isin(data['pl'])).ffill()

data['upper'] = data['ph'].where(data.index.isin(data['ph'])).ffill() - data['slope_ph']
data['lower'] = data['pl'].where(data.index.isin(data['pl'])).ffill() + data['slope_pl']

# Determine trade entries and exits
data['trade_type'] = None
data['entry_price'] = None
data['stop_loss'] = None
data['take_profit'] = None

for i in range(length, len(data)):
    if data['close'][i] > data['upper'][i-length]:
        data['trade_type'][i] = 'Long'
        data['entry_price'][i] = data['close'][i]
        data['stop_loss'][i] = data['entry_price'][i] - 0.02 * data['entry_price'][i]
        data['take_profit'][i] = data['entry_price'][i] + 0.03 * data['entry_price'][i]
    elif data['close'][i] < data['lower'][i-length]:
        data['trade_type'][i] = 'Short'
        data['entry_price'][i] = data['close'][i]
        data['stop_loss'][i] = data['entry_price'][i] + 0.02 * data['entry_price'][i]
        data['take_profit'][i] = data['entry_price'][i] - 0.03 * data['entry_price'][i]

data['trade_type'] = data['trade_type'].ffill()
data['entry_price'] = data['entry_price'].ffill()
data['stop_loss'] = data['stop_loss'].ffill()
data['take_profit'] = data['take_profit'].ffill()

data['exit_trade'] = False
for i in range(length, len(data)):
    if (
        (data['trade_type'][i] == 'Long' and data['low'][i] < data['stop_loss'][i]) or
        (data['trade_type'][i] == 'Short' and data['high'][i] > data['stop_loss'][i])
    ):
        data['exit_trade'][i] = True

# Calculate trading metrics
total_trades = len(data[data['entry_price'].notnull()])
positive_trades = len(data[(data['entry_price'].notnull()) & (data['close'] > data['entry_price'])])
win_rate = positive_trades / total_trades if total_trades > 0 else 0.0

# Plotting
plt.plot(data['close'][length:], label='Closing Price')
plt.plot(data['upper'][length:], label='Upper Trendline', color='#26a69a')
plt.plot(data['lower'][length:], label='Lower Trendline', color='#ef5350')

for i in range(len(data)):
    if not pd.isnull(data['entry_price'][i]):
        if data['trade_type'][i] == 'Long':
            plt.scatter(i, data['entry_price'][i], marker='^', color='r', label='Long Entry')
        else:
            plt.scatter(i, data['entry_price'][i], marker='v', color='g', label='Short Entry')
    if data['exit_trade'][i]:
        plt.scatter(i, data['stop_loss'][i], marker='x', color='b', label='Exit')

plt.legend()
plt.show()

I need suggestions about how to incorporate all the goals and make the code, more optimized and better.
Cheers!

Answer: Try using pands-ta the library is lighter and faster than ta-lib