Maximizing Profits with a KST-Based Python Trading Strategy
Written on
The KST (Know Sure Thing) indicator serves as a momentum oscillator aimed at pinpointing significant market trend shifts. Created by Martin J. Pring, it operates on four distinct time frames: 10, 15, 20, and 30 periods.
Calculation of the KST indicator involves a weighted moving average of the rate of change (ROC) values across these four time frames. The ROC quantifies the percentage change in price over a designated time. This KST value is subsequently refined through another moving average.
Traders can derive buy and sell signals from the KST indicator by observing the interaction between the KST line and a signal line. A KST line crossing above the signal line signals a buying opportunity, while a crossing below indicates a selling point.
Backtesting is essential for validating any trading strategy, enabling traders to analyze performance against historical data. This article will guide you through backtesting a trading strategy utilizing the KST indicator.
To kick off, we will import historical price data for the asset of interest and compute the KST indicator using the following code:
def calculate_KST(df, roc_periods=(10, 15, 20, 30), sma_periods=(10, 10, 10, 15)):
for i, r in enumerate(roc_periods):
df[f'ROC{i+1}'] = ((df['Close'] - df['Close'].shift(r)) / df['Close'].shift(r)) * 100weights = [1, 2, 3, 4]
for i, s in enumerate(sma_periods):
df[f'WMA{i+1}'] = df[[f'ROC{j+1}' for j in range(i+1)]] @ weights[:i+1] / sum(weights[:i+1])df['KST'] = df[[f'WMA{j+1}' for j in range(4)]] @ weights / sum(weights)
df['KSTS'] = df['KST'].rolling(window=9).mean()
return df
This function receives a DataFrame df containing historical price data, as well as two tuples—roc_periods and sma_periods—indicating the periods for ROC and moving average calculations. It returns the DataFrame augmented with columns for each of the four ROC values, the four weighted moving averages, KST, and KSTS (the smoothed KST).
df['KST'] = ta.trend.kst(close=df['close'], roc1=10, roc2=15, roc3=20, roc4=30, window1=10, window2=10, window3=10, window4=15, fillna=True)
df['KSTS'] = ta.trend.kst_sig(close=df['close'], roc1=10, roc2=15, roc3=20, roc4=30, window1=10, window2=10, window3=10, window4=15, nsig=9, fillna=True)
Next, we will establish our trading strategy that will generate buy signals when the KST line surpasses the KSTS line and sell signals when the KST line dips below the KSTS line. The code for this is as follows:
def generate_signals(df):
signals = []
for i in range(1, len(df)-1):
if df.iloc[i]['KSTS'] > df.iloc[i]['KST']:
signals.append(-1)elif df.iloc[i]['KST'] > df.iloc[i]['KSTS']:
signals.append(1)else:
signals.append(0)return signals
This function processes the DataFrame with KST and KSTS columns, generating a list of signals based on the crossings of these two lines.
Finally, we will conduct a backtest of our strategy using the historical price data and the signals generated. We will start with an initial capital of $1,000 and simulate trades with the following code:
Backtesting with ETHUSDT on a 1-Hour Timeframe
# Add the signals list to the dataframe as a new column
df["signal"] = signals
print(signals)
investment = 1000
current_investment = 1000
invested_amount = 0
fees = 0
profit = 0
is_invested = False
best_trade = -99999999
worst_trade = 99999999
largest_loss = 0
largest_gain = 0
total_trades = 0
loss_threshold = 0.03 # Stop loss threshold at 3%
loss_amount = 0 # variable to track loss amount
for i in range(500, len(df)):
signal = df.iloc[i]['signal']
close = df.iloc[i]['close']
if signal == 1 and not is_invested:
entry_point = close
print(signal)
print(close)
quantity = (current_investment / close)
print(current_investment)
print(quantity)
invested_amount = quantity * close
is_invested = True
elif signal == -1 and is_invested:
print(signal)
print('close', close)
profit = quantity * (close - entry_point) - 2
print('profit', profit)
current_investment += profit
invested_amount = 0
total_trades += 1
is_invested = False
if profit > largest_gain:
largest_gain = profitif profit < largest_loss:
largest_loss = profitif profit > best_trade:
best_trade = profitif profit < worst_trade:
worst_trade = profitloss_amount = 0 # reset loss_amount when position is closed
elif is_invested:
# check if stop loss threshold has been reached
loss = invested_amount - (quantity * close)
if loss > invested_amount * loss_threshold:
print("Stop loss triggered!")
profit = invested_amount * (1 - loss_threshold) - invested_amount - 2
current_investment += profit
invested_amount = 0
total_trades += 1
is_invested = False
if profit > largest_gain:
largest_gain = profitif profit < largest_loss:
largest_loss = profitif profit > best_trade:
best_trade = profitif profit < worst_trade:
worst_trade = profitloss_amount = 0 # reset loss_amount when position is closed
else:
loss_amount = loss # update loss_amountelse:
passfinal_profit = current_investment - investment
print("Final Profit: ", final_profit)
print("Best Trade: ", best_trade)
print("Worst Trade: ", worst_trade)
print("Largest Loss: ", largest_loss)
print("Largest Gain: ", largest_gain)
print("Total Trades: ", total_trades)
After executing the backtest on the historical data, the final portfolio value was $1,218,449. This outcome highlights the strategy's profitability, suggesting its potential for generating positive returns in future trades.